I am using firebase deeplinks to open my app with specific parameters. Therefore I am using the library https://ionicframework.com/docs/native/firebase-dynamic-links. In Android it's working fine. But in iOS, I do not receive the link in the web code base:
CapacitorFirebaseDynamicLinks.addListener(
"deepLinkOpen",
(data: { url: string }) => {
console.log("Listener started);
}
);
FirebaseDynamicLinks.onDynamicLink().subscribe(
async (res: IDynamicLink) => {
console.log(res.deepLink);
console.log(res.matchType);
}
);
I followed the instructions of the plugin and firebase docs (https://firebase.google.com/docs/dynamic-links/ios/receive) to set up the iOS part. Therefore, I implemented the continue and open url functions in AppDelegate.swift as described in firebase docs. Currently I am receiving the link in the continue function but I do not know how to forward it to the subscriber in the web code base.
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
let handled = DynamicLinks.dynamicLinks().handleUniversalLink(userActivity.webpageURL!) { (dynamiclink, error) in
print("link received");
print(dynamiclink);
print(error);
}
return handled
}
There is no documentation about it, i also tried another deeplink plugin (https://github.com/ClipboardHealth/capacitor-firebase-dynamic-links).
Related
My issue is only on iOS, Android is having no problem receiving the dynamic link
Dynamiclink is nil and when it isn't query params are not there
Firebase Setup
short link: https://examplebase.page.link/migration
dynamic link: https://www.examplelink.com/migrate
I've followed every video in firebase dynamic links documentation
long link I'm trying to use
https://examplebase.page.link/?link=https://www.examplelink.com/migrate?migrationToken=123&docNumber=123&apn=py.com.exam.hoc.debug&isi=11223344&ibi=py.com.exam.development
Before using the dynamic link I encode the deep link to look like this
https://examplebase.page.link/?link=https%3A%2F%2Fwww%2Eexamplelink%2Ecom%2Fmigrate%3FmigrationToken%3D123%26docNumber%3D123&apn=py.com.exam.hoc.debug&isi=11223344&ibi=py.com.exam.development
Where I process the dynamic link
func application(_ application: UIApplication, continue userActivity: NSUserActivity,
restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
let handled = DynamicLinks.dynamicLinks().handleUniversalLink(userActivity.webpageURL!) { dynamiclink, error in
if let migrationToken = dynamiclink?.url?.valueOf("migrationToken"),
let documentNumber = dynamiclink?.url?.valueOf("docNumber") {
if !migrationToken.isEmpty && !documentNumber.isEmpty {
//do stuff
} else {
// error
}
} else {
// error
}
}
return handled
}
Something to point out my short link has the path variable /migration I'm currently not using that in my long link, but if I do, the dynamic link works differently
Case 1 Using base link without the path variable
Inside this method DynamicLinks.dynamicLinks().handleUniversalLink(userActivity.webpageURL!) { dynamiclink, error in }
dynamiclink is nil even though userActivity.webpageURL! has the value of the link received. Error received
Deep Link does not contain valid required params. URL params: {
apn = "py.com.exam.hoc.debug";
efr = 1;
ibi = "py.com.exam.development";
isi = 1505794177;
link = "https://www.examplelink.com/migrate?migrationToken=95e7f2f9-178f-4533-8907-50b656757695&docNumber=4233512";
}
Case 2 Using base link with path variable
Inside this method DynamicLinks.dynamicLinks().handleUniversalLink(userActivity.webpageURL!) { dynamiclink, error in }
dynamiclink has the value of https://www.examplelink.com/migrate without the query param
In console I see same error than in Case 1
Following one tutorial from Firecast the guy says to set the dynamic link on firebase with the query params set, then my Firebase setup would look like this
Firebase Setup
dynamic link: https://www.examplelink.com/migrate?migrationToken=123&docNumber=123
The issue here is that the app receives those params hard coded, they are never replaced
Currently, I am triggering password reset like this:
static func firebasePasswordReset(email:String, responseError:#escaping (ResponseError?)->Void, completion:#escaping (
String)->Void){
Auth.auth().sendPasswordReset(withEmail: email) { (error) in
if(error != nil){
responseError(ResponseError.custom(error?.localizedDescription ?? "Unknow error occured. Please try again."))
}else{
completion(NSLocalizedString("Password reset link sent. Please check \(email).", comment: ""))
}
}
}
Though, everything works fine, and link to the appropriate email is sent, a user gets a link that I have set in Firebase console for my website.
So it is https://myprojectname/reset-password.html page.
Now, for iOS users, I don't want them to go to site to reset their password. I want to redirect them to an app, and open a form in their iOS app. Is this somehow possible?
1- AppDelegate
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
if let url = userActivity.webpageURL {
if url.path.range(of: "/reset-password.html") != nil {
if let passwordToken = url.getQueryItemValueForKey("token") {
let resetPasswordController= ResetPasswordController()
resetPasswordController?.passwordToken = passwordToken
self.window?.rootViewController = resetPasswordController
self.window?.makeKeyAndVisible()
}
}
}
return true
}
2 - Add your domain to associated-domains in entitlements.
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:www.yourdomain.com</string>
<string>applinks:yourdomain.com</string>
</array>
3- Create apple-app-site-association file
Create a apple-app-site-association file which's mime-type is json but without file extension. YOUR_APP_ID is someting like XXXXXXX.com.mycompany.projectname
And be sure it must be accesible via public https://yourdomain.com/apple-app-site-association with the content-type of application/json
{
"applinks": {
"apps": [],
"details": [
{
"appID": "YOUR_APP_ID",
"paths": [
"/reset-password.html*"
]
}
]
}
Update: You must also serve apple-app-site-association file with https.
https://developer.apple.com/library/archive/documentation/General/Conceptual/AppSearch/UniversalLinks.html
4- In the email, link should contain a password token, something like this,
Reset Your Password
5- Final
Create a password reset form in your ResetPasswordController,
Approach one: When user submit form send the token to your server. Check if token exist etc. return true or false from your API.
Approach two: Check if token exist via API then show your reset form.
I've been using p2-oauth2 library earlier to be able to log in through a safariViewController, but since the latest iOS version (11.3) I found out that my app were crashing all the time when the user tries to log in. I didn't get any error messages, but after a while I found out that SFAuthenticationSessions is the way to go when using SSO (single sign on).
My old code were pretty much like this (Using p2_oauth2):
static var oauth2 = OAuth2CodeGrant(settings: [
"client_id": "myClientID",
"authorize_uri": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
"token_uri": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
"scope": "User.Read Mail.Read Calendars.ReadWrite Calendars.Read Calendars.Read.Shared Offline_access",
"redirect_uris": ["myRedirectURI"],
"keychain": true
])
func loginToOffice365(completion: #escaping (_ error: Error? ) -> ()) {
var userDataRequest: URLRequest {
var request = URLRequest(url: URL(string: "https://graph.microsoft.com/v1.0/me/")!)
request.setValue("Bearer \(OauthManager.oauth2.accessToken ?? "")", forHTTPHeaderField: "Authorization")
return request
}
alamofireManager.request(userDataRequest).validate().responseJSON {
(response) in
switch response.result {
case .success( _):
//Handle user information
completion(nil)
case .failure(let error):
completion(error)
}
}
}
I tried to implement in SFAuthenticationSession in my project, and it was requiring a URL as a parameter. So I have been searching for a while for a Microsoft URL to make it possible to send in clientId, scope, and redirectURI in the same URL. And here's the result so far:
let url = URL(string: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?state=78E99F6B&response_type=code&scope=User.Read+Mail.Read+Calendars.ReadWrite+Calendars.Read+Calendars.Read.Shared&redirect_uri=MYREDIRECTURI&client_id=MYCLIENTID")!
OauthManager.authenticationSession = SFAuthenticationSession(url: url, callbackURLScheme: nil, completionHandler: { (successUrl: URL?, error: Error?) in
if let error = error {
print(error)
completion(error)
} else {
var accessToken = ""
if let absolutString = successUrl?.absoluteString, let urlComponents = URLComponents(string: absolutString)?.query {
accessToken = urlComponents
}
print(accessToken)
completion(nil)
}
})
OauthManager.authenticationSession?.start()
So now I finally received an access token from Microsoft. But where should I go from here? How do I get refresh tokens, make it possible to start calling Microsoft graph API calls?
If you know any better solution or any advice I'll be glad to receive them! This is my first project using login, because I'm fairly new to Swift.
Update:
I can also mention that Microsoft documentation recommends using these libraries:
Microsoft Authentication Library (MSAL) client libraries are
available for .NET, JavaScript, Android, and Objective-C. All
platforms are in production-supported preview, and, in the event
breaking changes are introduced, Microsoft guarantees a path to
upgrade.
Server middleware from Microsoft is available for .NET Core and
ASP.NET (OWIN OpenID Connect and OAuth) and for Node.js (Microsoft
Azure AD Passport.js).
The v2.0 endpoint is compatible with many third-party authentication
libraries.
https://developer.microsoft.com/en-us/graph/docs/concepts/auth_overview
I've tried MSAL and AppAuth, but they just didn't gave me any response back.
#Paulw11 found the answer.
I was using a method, which worked fine up until XCode 9.3 and iOS 11.3:
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
return true
}
But I had to change to the following method to make it work:
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
return true
}
This is fixed in Xcode 9.4 and worked fine as we tested it. There is indeed a problem on Xcode 9.3
I'm new to Parse so I'm building a test project to test out various capabilities. I was successful in building upstream data synchronisation, where I can push data to the Parse server. But I'm totally lost on how to enable downstream data sync.
I want to make it so that my iOS app receives updates from the server when there's new/updated data. For example, after insert a new record using the parse.com interface, I want to get notified and be able to then pull the new data.
My Parse setup:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
Parse.enableLocalDatastore()
Parse.setApplicationId("ID", clientKey: "KEY")
return true
}
My data transfer so far relied on using instances of PFObject.
userParseObject = PFObject(className: "User")
userParseObject["dateCreated"] = dateCreated
userParseObject["dateOfLastLogin"] = dateOfLastLogin
userParseObject["name"] = name
userParseObject.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {} else {}
}
How do I listen for CRUD operations on the Parse server?
My WatchKit extension makes heavy use of openParentApplication to load data and images into the WatchKit app, as adviced by Apple. This is working fine, especially after using the clever advice here:
http://www.fiveminutewatchkit.com/blog/2015/3/11/one-weird-trick-to-fix-openparentapplicationreply
However, it's only working for about 15 minutes. After that, the WatchKit extension fails to wake up the iPhone application in the background and the iOS AppDelegate handleWatchKitExtensionRequest is never called. If I manually open the iPhone app, the call gets through and it answers directly to the WatchKit extension, but that is hardly something I can ask the user to do... I want the WatchKit app to usable without having to manually wake up the iOS app every 15 minutes.
I'm using iOS 8.2 on the testing device, Swift and Xcode 6.2. Maybe it could be related to using a developer provisioning profile? Anyone else out there having experienced this?
Code used in utility class WatchUtils, this is used on several occations in the app:
class func openPhoneApp(userInfo: [NSObject : AnyObject], complete:(reply:[NSObject : AnyObject]!, error:NSError!) -> Void) {
WKInterfaceController.openParentApplication(userInfo, reply: { (reply:[NSObject : AnyObject]!, error:NSError!) -> Void in
complete(reply:reply, error:error)
})
}
openPhoneApp is used like this:
WatchUtils.openPhoneApp(requestData, complete: { (reply, error) -> Void in
if let reply = reply {
// Do stuff
}
})
AppDelegate:
func application(application: UIApplication!, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]!, reply: (([NSObject : AnyObject]!) -> Void)!) {
// Bogus task for keeping app going
var bogusTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler { () -> Void in
}
// End bogus task after 2 seconds
delay(2.0, closure: { () -> () in
UIApplication.sharedApplication().endBackgroundTask(bogusTask)
})
// Code here for collecting information from phone app
// ...
var replyDict:[NSObject : AnyObject] = ["userHighscore":45]
reply(replyDict)
UIApplication.sharedApplication().endBackgroundTask(bogusTask) // End the bogusTask in case the work was faster than 2 seconds
}
// Utility function for delay
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(),
closure
)
}
I had the same problem and reading the docs fixed it for me.
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/#//apple_ref/occ/intfm/UIApplicationDelegate/application:handleWatchKitExtensionRequest:reply:
Because this method is likely to be called while your app is in the background, call the beginBackgroundTaskWithName:expirationHandler: method at the start of your implementation and the endBackgroundTask: method after you have processed the reply and executed the reply block. Starting a background task ensures that your app is not suspended before it has a chance to send its reply.
Some of my code:
nint taskId = 0;
public override void HandleWatchKitExtensionRequest (UIApplication application, NSDictionary userInfo, Action<NSDictionary> reply)
{
try {
taskId= UIApplication.SharedApplication.BeginBackgroundTask(delegate
{ //this is the action that will run when the task expires
});
if (userInfo != null && userInfo.Keys [0] != null && userInfo.Keys [0].ToString() == "match") {
MyWebClient wc = new MyWebClient ();
wc.Encoding = Encoding.UTF8;
wc.DownloadStringCompleted += (object sender, DownloadStringCompletedEventArgs e) => {
String res = e.Result;
reply (new NSDictionary (
"justTesting", new NSString (res)
));
if(taskId != 0)
UIApplication.SharedApplication.EndBackgroundTask(taskId);
};
wc.DownloadStringAsync(new Uri(myUrl));
}
}
After I changed to doing this it has been rock stable for days for me while previously it always stopped working after 5-15 minutes.