Magic Link Registration for Web Accounts
This document assumes that your application has been configured using Push Notification Setup.
HYPR SDK allows for registration using deep links and Magic Links.
SDK for Android
Setup
Follow the Firebase documentation for setting up Dynamic Links for your Android App.
To support Firebase Dynamic Links for registration, add the following Firebase dependencies in app/webaccountunlock/app/build.gradle
:
dependencies {
// Out of Band Authentication
implementation platform('com.google.firebase:firebase-bom:31.1.0')
implementation 'com.google.firebase:firebase-messaging:23.3.1'
}
Within AndroidManifest.xml
and the activity that handles the Magic Link, specify the Uniform Resource Identifiers (URIs) to which it will respond:
<activity android:name=".MainActivity">
<intent-filter>
<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" />
<!-- Accepts URIs that begin with "hypr://register” -->
<data
android:host="register"
android:scheme="hypr" />
<!-- Accepts URIs that begin with "http://hypr.com/mobileregister” -->
<data
android:host="hypr.com"
android:pathPrefix="/mobileregister"
android:scheme="http" />
<!-- Accepts URIs that begin with "https://hypr.com/dynamiclink” -->
<data
android:host="hypr.com"
android:pathPrefix="/dynamiclink"
android:scheme="https" />
<!-- note that the leading "/" is required for pathPrefix-->
</intent-filter>
</activity>
Place the following code within the activity that processes the Dynamic Link:
@Override
protected void onResume() {
super.onResume();
onResumeStart();
}
public boolean doesIntentContainDynamicLink() {
boolean doesIntentContainDynamicLink = getIntent() != null
&& getIntent().getData() != null
&& getIntent().getData().toString().contains("https://hyprapp.page.link");
return doesIntentContainDynamicLink;
}
public void startDynamicLinkIntent() {
if (getIntent() != null) {
FirebaseDynamicLinks.getInstance()
.getDynamicLink(getIntent())
.addOnSuccessListener(this, new OnSuccessListener<PendingDynamicLinkData>() {
@Override
public void onSuccess(PendingDynamicLinkData pendingDynamicLinkData) {
setIntent(null);
if (pendingDynamicLinkData != null) {
Uri uri = pendingDynamicLinkData.getLink();
Intent magicLinkIntent = new Intent();
magicLinkIntent.setData(uri);
onNewIntent(magicLinkIntent);
onResumeStart();
}
}
});
}
}
private void onResumeStart(){
if (isMagicLinkUriAvailable()) {
HyprActions.getInstance().addMachineWithMagicLink(this, new HyprActionCallbacks.HyprActionCallback() {
@Override
public void onSuccess(@NonNull HyprStatusResult hyprStatusResult) {
toast("sucess");
}
@Override
public void onFailure(@NonNull HyprStatusResult hyprStatusResult) {
toast("fail");
}
});
}
else if(doesIntentContainDynamicLink()) {
startDynamicLinkIntent();
}
}
private boolean isMagicLinkUriAvailable() {
boolean isMagicLinkUriAvailable = HyprApp.getDbAdapter().isMagicLinkUriAvailable(this);
return isMagicLinkUriAvailable;
}
SDK for iOS
Follow the Firebase documentation for setting up Dynamic Links for your iOS App.
To implement Magic Links for the HYPR SDK for iOS, you will use the DynamicLinks target within the HYPR iOS Reference App.
You will use the following four classes:
-
DynamicLinksViewController
-
AppDelegate
-
HYPRWrapper+WebAccount
-
CustomFirebaseAdapter
You will also need the approprate .plist
files.
Starting with DynamicLinksViewController
, add the following code:
import UIKit
import HyprCore
class DynamicLinksViewController: UIViewController {
var hyprWrapper: HYPRWrapperProtocol!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
let label = UILabel()
label.numberOfLines = 2
label.text = "Deeplink\nReference"
label.textAlignment = .center
label.textColor = UIColor.gray
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)
])
}
}
Within your AppDelegate
class, ensure you import the following and the code below:
import UIKit
import HyprCore
import CoreLocation
import FirebaseCore
import FirebaseDynamicLinks
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
internal func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window?.rootViewController = DynamicLinksViewController()
guard let dynamicLinkViewController = window?.rootViewController as? DynamicLinksViewController else {
return false
}
dynamicLinkViewController.hyprWrapper = HYPRWrapper.shared
HYPRWrapper.initHYPRForWebAccountUnlock(httpHeaderAdapter: httpHeaderAdapter())
return true
}
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
let handled = DynamicLinks.dynamicLinks().handleUniversalLink(userActivity.webpageURL!) { [self] dynamiclink, error in
guard let dynamicURL = dynamiclink?.url else {
print("An error occurred: \(error?.localizedDescription ?? "Unknown Error")")
return
}
let (extractedURL, extractedToken) = extractURLAndToken(from: dynamicURL)
handleExtractedURLAndToken(extractedURL, extractedToken)
}
return handled
}
func extractURLAndToken(from url: URL) -> (String?, String?) {
var extractedURL: String?
var extractedToken: String?
if let components = URLComponents(url: url, resolvingAgainstBaseURL: false) {
if let queryItems = components.queryItems {
for queryItem in queryItems {
print("Parameter \(queryItem.name) is \(queryItem.value ?? "")")
if queryItem.name == "rpUrl" {
extractedURL = queryItem.value
}
if queryItem.name == "token" {
extractedToken = queryItem.value
}
}
}
}
return (extractedURL, extractedToken)
}
func handleExtractedURLAndToken(_ url: String?, _ token: String?) {
if let url = url, let token = token {
if let dynamicLinksViewController = window?.rootViewController as? DynamicLinksViewController {
HYPRWrapper.shared.pairMachineWithDeepLink(parentViewController: dynamicLinksViewController, url: url, token: token) { error in
DispatchQueue.main.async {
let title = error == nil ? "Success" : "Error"
let message = error?.localizedDescription.debugDescription ?? "Successfully paired the deep link."
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
dynamicLinksViewController.present(alert, animated: true, completion: nil)
}
}
}
} else {
print("Either extractedURL or extractedToken is nil")
}
}
func application(_ app: UIApplication, open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool {
return application(app, open: url,
sourceApplication: options[UIApplication.OpenURLOptionsKey
.sourceApplication] as? String,
annotation: "")
}
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
return DynamicLinks.dynamicLinks().dynamicLink(fromCustomSchemeURL: url) != nil
}
func httpHeaderAdapter() -> HTTPHeaderAdapter {
let httpHeaderAdapter = HTTPHeaderAdapter.shared
httpHeaderAdapter.deviceInfo = DeviceInfo()
return httpHeaderAdapter
}
}
Add the HYPRWrapper+WebAccount
class with the code below (be sure to import HyprCore
):
import HyprCore
extension HYPRWrapper {
class func initHYPRForWebAccountUnlock(httpHeaderAdapter:HYPRCustomHeadersAdapter?) {
HYPRUserAgent.setNotificationProviderAdapter(CustomFirebaseAdapter.shared)
CustomFirebaseAdapter.shared.userPushNotifications(enabled: true)
initHYPR(httpHeaderAdapter: httpHeaderAdapter)
}
func handlePushNotificationForAuthentication(window: UIWindow, userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
let tempRootViewController = window.rootViewController
window.rootViewController = UIViewController()
if let rootVC = window.rootViewController {
rootVC.view.backgroundColor = UIColor.white
HYPRUserAgent.setParentViewController(rootVC)
let didHandle = CustomFirebaseAdapter.handlePushPayloadUserInfo(userInfo, completion: { (userInfo, error) in
window.rootViewController = tempRootViewController
if let error = error {
print("Encountered an error during OOB Authentication: \(error.localizedDescription)")
}
})
completionHandler(didHandle ? .newData : .noData)
}
}
}
The last class to add is CustomFirebaseAdapter
.
import UIKit
import HyprCore
import Firebase
import FirebaseInAppMessaging
class CustomFirebaseAdapter: NSObject, UNUserNotificationCenterDelegate, UIApplicationDelegate {
var pushHandlerAction: HYPRNotificationHandlerAction?
static let shared = CustomFirebaseAdapter()
override init() {
super.init()
configureAdapter()
}
func configureAdapter() {
FirebaseApp.configure()
Messaging.messaging().delegate = self
}
}
// MARK: - Messaging Delegate
extension CustomFirebaseAdapter: MessagingDelegate {
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
print("Received Registration token: \(fcmToken)")
let userInfo = [HYPRPushNotificationTokenKey:fcmToken]
NotificationCenter.default.post(name: NSNotification.Name(rawValue: HYPRPushNotificationRegistrationUpdated),
object: nil,
userInfo: userInfo)
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
application.delegate?.application?(application, didReceiveRemoteNotification: userInfo, fetchCompletionHandler: { _ in
})
}
}
extension CustomFirebaseAdapter: HYPRPushNotificationAdapter {
func reset(completion: @escaping (Error?) -> Void) {
Messaging.messaging().deleteFCMToken(forSenderID: providerAppIdentifier()) { error in
DispatchQueue.main.async {
completion(error)
}
}
}
class func handlePushPayloadUserInfo(_ userInfo:[AnyHashable : Any], completion: @escaping (HYPRNotificationHandlerCompletion)) -> Bool {
return CustomFirebaseAdapter.shared.handlePushPayload(userInfo: userInfo, completion: completion)
}
// this is the private, instance implementation
func handlePushPayload(userInfo:[AnyHashable : Any], completion: @escaping (([AnyHashable : Any]?, Error?) -> Void)) -> Bool {
if let pushHandlerAction = self.pushHandlerAction {
return pushHandlerAction(userInfo, completion)
}
else {
print("Error: Could not handle push because pushHandlerAction is nil")
return false
}
}
func devicePushIdentifier() -> String {
return Messaging.messaging().fcmToken ?? "No FCM Token"
}
func providerAppIdentifier() -> String {
return FirebaseOptions.defaultOptions()?.gcmSenderID ?? "No GCM Sender ID"
}
func areNotificationsAuthorized() -> Bool {
let current = UNUserNotificationCenter.current()
var authStatus = false
let sem = DispatchSemaphore(value: 1)
current.getNotificationSettings(completionHandler: { (settings) in
if settings.authorizationStatus == .authorized {
authStatus = true
}
sem.signal()
})
sem.wait()
return authStatus
}
func userPushNotificationsEnabled() -> Bool {
return UIApplication.shared.isRegisteredForRemoteNotifications
}
func userPushNotifications(enabled: Bool) {
}
func enableUserPushNotifications() {
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .sound, .badge]
UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { (granted, error) in
if(granted) {
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
}
func setPushNotificationHandler(handlerAction: @escaping HYPRNotificationHandlerAction) {
pushHandlerAction = handlerAction
}
}