package com.hokolinks.model; import android.content.Context; import com.hokolinks.deeplinking.listeners.MetadataRequestListener; import com.hokolinks.utils.Utils; import com.hokolinks.utils.log.HokoLog; import com.hokolinks.utils.networking.Networking; import com.hokolinks.utils.networking.async.HttpRequest; import com.hokolinks.utils.networking.async.HttpRequestCallback; import com.hokolinks.utils.networking.async.NetworkAsyncTask; import org.json.JSONException; import org.json.JSONObject; import java.util.Arrays; import java.util.HashMap; import java.util.List; /** * Deeplink is the model which represents an inbound or outbound deeplink object. * It contains a route format string, the route parameters, the query parameters and an optional * url scheme. */ public class Deeplink { // Key values from incoming deeplinks private static final String SMARTLINK_CLICK_IDENTIFIER_KEY = "_hk_cid"; private static final String SMARTLINK_IDENTIFIER_KEY = "_hk_sid"; private static final String METADATA_KEY = "_hk_md"; private static final String METADATA_PATH = "smartlinks/metadata"; private String mRoute; private HashMap<String, String> mRouteParameters; private HashMap<String, String> mQueryParameters; private JSONObject mMetadata; private String mURLScheme; private HashMap<String, JSONObject> mURLs; private String mDeeplinkURL; private boolean mIsDeferred; private boolean mWasOpened; private boolean mIsUnique; /** * The constructor for Deeplink objects. * * @param urlScheme Optional url scheme. * @param route A route in route format. * @param routeParameters A HashMap where the keys are the route components and the values are * the route parameters. * @param queryParameters A HashMap where the keys are the query components and the values are * the query parameters. * @param metadata A JSONObject containing metadata to be passed to whoever opens the deeplink. * @param deeplinkURL The actual deeplink url opened by the app. * @param isDeferred true in case the deeplink came from a deferred deeplink, false otherwise. * @param isUnique true in case the deeplink should be unique, false otherwise. */ public Deeplink(String urlScheme, String route, HashMap<String, String> routeParameters, HashMap<String, String> queryParameters, JSONObject metadata, String deeplinkURL, boolean isDeferred, boolean isUnique) { if (urlScheme == null) mURLScheme = ""; else mURLScheme = urlScheme; mRoute = route; mMetadata = metadata; mRouteParameters = routeParameters != null ? routeParameters : new HashMap<String, String>(); mQueryParameters = queryParameters != null ? queryParameters : new HashMap<String, String>(); mURLs = new HashMap<>(); mDeeplinkURL = deeplinkURL; mIsDeferred = isDeferred; mIsUnique = isUnique; } /** * An easy to use static function for the developer to generate their own deeplinks to * generate Smartlinks afterwards. * * @return The generated Deeplink. */ public static Deeplink deeplink() { return deeplink(null); } /** * An easy to use static function for the developer to generate their own deeplinks to * generate Smartlinks afterwards. * * @param route A route in route format. * @return The generated Deeplink. */ public static Deeplink deeplink(String route) { return deeplink(route, null); } /** * An easy to use static function for the developer to generate their own deeplinks to * generate Smartlinks afterwards. * * @param route A route in route format. * @param routeParameters A HashMap where the keys are the route components and the values are * the route parameters. * @return The generated Deeplink. */ public static Deeplink deeplink(String route, HashMap<String, String> routeParameters) { return deeplink(route, routeParameters, null); } /** * An easy to use static function for the developer to generate their own deeplinks to * generate Smartlinks afterwards. * * @param route A route in route format. * @param routeParameters A HashMap where the keys are the route components and the values are * the route parameters. * @param queryParameters A HashMap where the keys are the query components and the values are * the query parameters. * @return The generated Deeplink. */ public static Deeplink deeplink(String route, HashMap<String, String> routeParameters, HashMap<String, String> queryParameters) { return deeplink(route, routeParameters, queryParameters, null); } /** * An easy to use static function for the developer to generate their own deeplinks to * generate Smartlinks afterwards. * * @param route A route in route format. * @param routeParameters A HashMap where the keys are the route components and the values are * the route parameters. * @param queryParameters A HashMap where the keys are the query components and the values are * the query parameters. * @param metadata A JSONObject containing metadata to be passed to whoever opens the deeplink. * @return The generated Deeplink. */ public static Deeplink deeplink(String route, HashMap<String, String> routeParameters, HashMap<String, String> queryParameters, JSONObject metadata) { return deeplink(route, routeParameters, queryParameters, metadata, false); } /** * An easy to use static function for the developer to generate their own deeplinks to * generate Smartlinks afterwards. * * @param route A route in route format. * @param routeParameters A HashMap where the keys are the route components and the values are * the route parameters. * @param queryParameters A HashMap where the keys are the query components and the values are * the query parameters. * @param metadata A JSONObject containing metadata to be passed to whoever opens the deeplink. * @param isUnique true in case the deeplink should be unique, false otherwise. * @return The generated Deeplink. */ public static Deeplink deeplink(String route, HashMap<String, String> routeParameters, HashMap<String, String> queryParameters, JSONObject metadata, boolean isUnique) { Deeplink deeplink = new Deeplink(null, Utils.sanitizeRoute(route), routeParameters, queryParameters, metadata, null, false, isUnique); if (matchRoute(deeplink.getRoute(), deeplink.getRouteParameters()) || (route == null && routeParameters == null && queryParameters == null && metadata == null)) { return deeplink; } return null; } public static boolean matchRoute(String route, HashMap<String, String> routeParameters) { List<String> routeComponents = Arrays.asList(route.split("/")); for (int index = 0; index < routeComponents.size(); index++) { String routeComponent = routeComponents.get(index); if (routeComponent.startsWith(":") && routeComponent.length() > 2) { String token = routeComponent.substring(1); if (!routeParameters.containsKey(token)) { return false; } } } return true; } /** * Allows the developer to add a custom deeplink for a given platform. * * @param url The deeplink URL to be used on the platform. * @param platform The platform (from the DeeplinkPlatform enum). */ public void addURL(String url, DeeplinkPlatform platform) { try { JSONObject urlJSON = new JSONObject(); urlJSON.put("link", url); mURLs.put(stringForPlatform(platform), urlJSON); } catch (JSONException e) { HokoLog.e(e); } } /** * Logic behind the deeplink needing to request the server for metadata. * * @return true if the server should get metadata and doesn't have it already, false otherwise. */ public boolean needsMetadata() { return hasMetadataKey() && mMetadata == null; } private String stringForPlatform(DeeplinkPlatform platform) { switch (platform) { case IPHONE: return "iphone"; case IPAD: return "ipad"; case IOS_UNIVERSAL: return "universal"; case ANDROID: return "android"; case WEB: return "web"; default: return null; } } public boolean hasURLs() { return mURLs.size() > 0; } /** * This function serves the purpose of communicating to the Hoko backend service that a given * inbound deeplink object was opened either through the notification id or through the * deeplink id. * * @param token The Hoko API Token. */ public void post(String token, Context context) { if (isSmartlink()) { Networking.getNetworking().addRequest( new HttpRequest(HttpRequest.HokoNetworkOperationType.POST, "smartlinks/open", token, smartlinkJSON(context).toString())); } } /** * Requests metadata for the Deeplink object from the HOKO server. * Will call the listener after the request is complete. * @param token The HOKO SDK token. * @param metadataRequestListener A listener to know when the task completes. */ public void requestMetadata(String token, final MetadataRequestListener metadataRequestListener) { if (needsMetadata()) { new NetworkAsyncTask(new HttpRequest(HttpRequest.HokoNetworkOperationType.GET, HttpRequest.getURLFromPath(METADATA_PATH), token, metadataJSON().toString()) .toRunnable(new HttpRequestCallback() { @Override public void onSuccess(JSONObject jsonObject) { Deeplink.this.mMetadata = jsonObject; if (metadataRequestListener != null) { metadataRequestListener.completion(); } } @Override public void onFailure(Exception e) { if (metadataRequestListener != null) { metadataRequestListener.completion(); } } })).execute(); } } public String getURL() { String url = this.getRoute(); if (this.getRouteParameters() != null) { for (String routeParameterKey : this.getRouteParameters().keySet()) { url = url.replace(":" + routeParameterKey, this.getRouteParameters() .get(routeParameterKey)); } } if (this.getRouteParameters() != null && this.getQueryParameters().size() > 0) { url = url + "?"; for (String queryParameterKey : this.getQueryParameters().keySet()) { url = url + queryParameterKey + "=" + this.getQueryParameters() .get(queryParameterKey) + "&"; } url = url.substring(0, url.length() - 1); } return url; } /** * Serves the purpose of returning a Deeplink in JSON form (useful for PhoneGap SDK) * * @return Deeplink in JSON form. */ public JSONObject toJSON() { JSONObject jsonObject = new JSONObject(); try { jsonObject.put("route", getRoute()); jsonObject.put("routeParameters", new JSONObject(mRouteParameters)); jsonObject.put("queryParameters", new JSONObject(mQueryParameters)); jsonObject.putOpt("metadata", getMetadata()); } catch (JSONException e) { HokoLog.e(e); } return jsonObject; } /** * Converts all the Deeplink information into a JSONObject to be sent to the Hoko backend * service. * * @return The JSONObject representation of Deeplink. */ public JSONObject json() { try { JSONObject root = new JSONObject(); root.putOpt("uri", getURL()); root.putOpt("metadata", getMetadata()); root.putOpt("unique", isUnique()); if (hasURLs()) root.putOpt("routes", new JSONObject(mURLs)); return root; } catch (JSONException e) { return null; } } /** * Converts the Deeplink into a JSONObject referring the Smartlink that was opened. * * @return The JSONObject representation of the Smartlink. */ private JSONObject smartlinkJSON(Context context) { JSONObject root = new JSONObject(); try { root.put("deeplink", mDeeplinkURL); root.put("uid", Device.getDeviceID(context)); } catch (JSONException e) { HokoLog.e(e); } return root; } private JSONObject metadataJSON() { try { if (getSmartlinkClickIdentifier() != null) { return new JSONObject().put(SMARTLINK_CLICK_IDENTIFIER_KEY, getSmartlinkClickIdentifier()); } else { return new JSONObject().put(SMARTLINK_IDENTIFIER_KEY, getSmartlinkIdentifier()); } } catch (JSONException e) { HokoLog.e(e); } return null; } public String toString() { String urlScheme = mURLScheme != null ? mURLScheme : ""; String route = mRoute != null ? mRoute : ""; String routeParameters = mRouteParameters != null ? mRouteParameters.toString() : ""; String queryParameters = mQueryParameters != null ? mQueryParameters.toString() : ""; String metadata = mMetadata != null ? mMetadata.toString() : ""; return "<Deeplink> URLScheme='" + urlScheme + "' route ='" + route + "' routeParameters='" + routeParameters + "' queryParameters='" + queryParameters + "' metadata='" + metadata + "'"; } public String getURLScheme() { return mURLScheme; } public HashMap<String, String> getRouteParameters() { return mRouteParameters; } public HashMap<String, String> getQueryParameters() { return mQueryParameters; } public JSONObject getMetadata() { return mMetadata; } public void setMetadata(JSONObject metadata) { mMetadata = metadata; } public String getRoute() { return mRoute; } private String getSmartlinkClickIdentifier() { return mQueryParameters.get(SMARTLINK_CLICK_IDENTIFIER_KEY); } private String getSmartlinkIdentifier() { return mQueryParameters.get(SMARTLINK_IDENTIFIER_KEY); } private boolean hasMetadataKey() { return mQueryParameters.containsKey(METADATA_KEY); } private boolean isSmartlink() { return getSmartlinkClickIdentifier() != null; } public boolean wasOpened() { return mWasOpened; } public void setWasOpened(boolean wasOpened) { mWasOpened = wasOpened; } public boolean isDeferred() { return mIsDeferred; } public boolean isUnique() { return mIsUnique; } }