SECRET OF CSS

Giving Firebase Dynamic Links macOS Support | by Ben Myers | Sep, 2022


Reverse engineer the Firebase iOS SDK

Firebase Dynamic Links macOS Support
Image by author

According to their documentation, Firebase Dynamic Links are only supported on iOS. I’ll show you how I added macOS support for Dynamic Links by reverse-engineering the Firebase iOS SDK.

I was happily building my iOS and macOS cross-platform application using Firebase until I learned that Firebase Dynamic Links only worked on iOS. I wanted to integrate online sessions into my app, and I used Dynamic Links as a way for other users to join in a session.

So finding out Dynamic Links weren’t supported on macOS was a disappointment. But I had to verify this.

1*NEDiMfboHrkP4DECdCPMUQ
The Firebase Dynamic Links target is only supported on iOS.

The proof is in Firebase’s Package.swift: the Dynamic Links target is only supported for iOS. Bummer!

But that got me thinking… why? The core features of Firebase Dynamic Links library in the SDK are

  • creating a Dynamic Link from the deep link
  • adding fallback URL’s, social parameters, minimum versions, and other specifiers to the Dynamic Link
  • shortening the Dynamic Link
  • and handling the completed link.

Clearly, it seems Firebase should be handling this stuff. And we trust what they develop in their SDK; surely it must work!

But what’s stopping us from making our own API to get around the macOS issue? A few URL requests from a few Firebase endpoints; some manual URL parsing and creation… this isn’t too bad!

But before we dive into the code, let’s get up to speed on how Dynamic Links work.

OK, so you’ve read the Dynamic Links documentation before, but maybe it’s time for a quick refresher on the relevant vocabulary. Jargon like “deep link”, “universal link”, “fallback URL”, etc. can get confusing. So, let’s clarify what Google means.

1*KDnn
A quick overview of the main differences between a Deep, Dynamic, and Short Dynamic link.
  • In the mobile context, A deep link uses a uniform resource identifier (URI) that links to a specific location within a mobile app rather than simply launching the app. It can be used to open, for instance, the App Store if your app is not installed.
  • A Dynamic Link is a link hosted and managed by Firebase. It carries information about your app (like bundle ID, and app store ID), social meta parameters, and more. The neat selling feature of Dynamic Links is cross-platform compatibility with many platforms, like iOS, Android, Flutter, etc. The payload of your Dynamic Link is your deep link.
  • A universal link is a link that could be your Dynamic Link and is handled by your app. Firebase will attempt to resolve a universal link as a Dynamic Link so it can give you the real payload: the deep link.

Dynamic Links are generated with a URL Prefix. In Firebase, by default, your URL prefix is generated to be your-firebase-project.page.link.

If you use a custom domain, you can create a URL prefix, such asmy-website.com/link.

  • A short Dynamic Link is a shortened version of your dynamic link. Like the normal Dynamic Link, it starts with your URL prefix. However, the query parameters in the Dynamic Link are shortened to a random string of 4 (or more) characters.

These various link types seem wonderful. Let’s now review how dynamic links work in action:

Creating Links

  1. First, we need to build our deep link.
  2. Grab your website’s domain. For example, I’ll use app.com.
  3. Choose a path for your content. I want to link to my user model, so my path will be /user. Paths are optional, and you can get creative as much as you’d like to customize in-app handling.
  4. Add query parameters for extra in-app handling. For instance, I use ?id=ABCDEFG to specify that I will be fetching the user with ID “ABCDEFG”.
  5. You now have your deep link payload. In my example, this is https://app.com/user?id=ABCDEFG.
  6. Next, grab your Domain URL Prefix. I’ll use app.page.link.
  7. From here, you use Firebase iOS SDK to create a “long” Dynamic Link with additional parameters, like the minimum app version needed, or your app’s App Store ID:
guard let link = URL(string: "https://app.com/user?id=ABCDEFG") else { return }
let dynamicLinksDomainURIPrefix = "https://app.page.link"
let linkBuilder = DynamicLinkComponents(link: link, domainURIPrefix: dynamicLinksDomainURIPRefix)
linkBuilder.iOSParameters = DynamicLinkIOSParameters(bundleID: "com.example.ios")
linkBuilder.androidParameters = DynamicLinkAndroidParameters(packageName: "com.example.android")
guard let longDynamicLink = linkBuilder.url else { return }
print("The long URL is: \(longDynamicLink)")

8. More often than not, your long Dynamic Link will be… long… and you’ll need to shorten it:

DynamicLinkComponents.shortenURL(url: longDynamicLink, options: nil) { url, warnings, error in
guard let url = url, error != nil else { return }
print("The short URL is: \(url)")
}

9. Once shortened, you’ll have your shareable Dynamic Link. Mine looks like https://app.page.link/ABCD.

Handling Links

  1. OK, so we’ve opened the URL in our app. In SwiftUI, this looks like:
MyView()
.onOpenURL { url in
// Handle universal url here.
}

2. We use the SDK to handle the (universal) URL to resolve the Dynamic Link:

DynamicLinks.dynamicLinks().handleUniversalLink(url)
{ dynamicLink, error in
// Dynamic link passed here
}

3. We obtain the deep link URL from the Dynamic Link:

let deepURL: URL? = dynamicLink?.url

4. That deep link contains our payload as described earlier. For instance, I can open a user’s profile page:

// deepURL = "https://app.com/user?id=ABCDEFG"if deepURL.path == "/user" {
let components = URLComponents(string: deepURL.absoluteString)
let userID = components?.queryItems.first?.value
// Handle opening user ID here
}

Boom. We’re done. Links created and handled.

But none of this works on macOS. That’s a problem, as I want to be able to at the very least create links from my desktop app (or any other Swift-running platform).

So, let’s see if we can pick apart firebase-ios-sdk, and the Firebase documentation, to accomplish our goal.

Let’s start with creating links. The best place to start is Firebase’s Dynamic Link documentation. Normally, we’d head over to the iOS section. However, do you notice another section in the documentation that could help us with bringing Dynamic Links over to macOS?

Can you find the section here that will help us bring Dynamic Link creation to any device?

It’s Manual URL Construction. I found this page while exploring the docs, and it’s what initially gave me the hope that this endeavor would be possible on macOS.

Check it out:

Firebase documentation shows us that a Dynamic Link can be manually constructed.

Using the typical URL prefix and query parameters, we can embed our deep link payload (?link=), and all of our metadata (i.e. &apn=, &amv=, &afl=), all locally:

var builder = URLComponents()
builder.scheme = "https"
builder.host = "app.page.link"
builder.queryItems = [
.init(name: "link", value: "https://app.com/user?id=ABCDEFG"),
.init(name: "ibi", value: "com.myapp.bundleid"),
// add other items, like "isi", "imv", "ofl", "st", "sd"...
]
let longDynamicLink: URL? = builder.url

Swift’s URLComponents will gloriously convert this into a properly-formatted URL, which is our long Dynamic Link.

Now, it’s time to shorten our long Dynamic Link. This is where doing REST work ourselves will really shine.

We know the shorten method provided by the SDK has an escaping completion handler, meaning something backend-related must be going on.

In fact, that is exactly the case: a POST request is called. The request contains information about the long Dynamic Link and the desired shortened link of the new URL.

Jumping to the definition of Firebase SDK’s shortenURL method, we can see a URL request is made on line 518:

1*DeqsLrBhsIC5 QsYKY2r0Q
The Firebase iOS SDK is making a URL request on line 518.

Digging deeper, we see that the “shortening request” is actually a long-winded POST request… called it!

1*iIW3ay0mLabuAmLJj1SSRQ
Lines 661–663 give us some of the information we need to make a POST request to shorten links.

Exploring this method gives us some key information:

{
"longDynamicLink": longDynamicLink,
"suffix": ["option": "SHORT"]
}
  • And, of course, the method is POST with JSON content type.

We’ve done our digging, so now it’s time to cheat and look at the docs. Heading over to the REST section in Create Dynamic Links shows us how to shorten a long Dynamic Link via REST:

1* GvO5 n7W Zyml8Wo0tgog
Firebase documentation shows us how to shorten a long Dynamic Link via REST.

Groovy! This matches what we just set up.

Sadly, this is as far as the documentation will take us. When it comes to handling URLs on our devices, there is no information about using URL requests to resolve a Deep Link, or any other information, from a shortened Dynamic Link.

The good news? We know how to dig through the SDK. Let’s inspect DynamicLinks.dynamicLinks().handleUniversalLink(_:) and see what we can find:

1*alpZTdlolDju7mUogfB3mQ

It looks like handleUniversalLink calls another method, resolveShortLink, on line 467. Jumping to the definition of resolveShortLink on line 490, we gather more intel:

1*LR7OY3ABdKiKH9PYpQnnTQ
Line 183 gives us information about the API endpoint we’re calling.

Notice that inspecting “/reopenAttribution”, kiOSReopenRestBaseUrl, and FIRDynamicLinkAPIKeyParameter(_APIKey) are all used to form the basic API endpoint needed to handle our Dynamic Link:

As for the POST request body, let’s scroll down a bit:

1*52FGdYizYfxM jMoeCg0vA
Lines 142–146 tell us about the POST request body.

We can see the POST body is in the form:

{
"requestedLink": url.absoluteString,
"bundle_id": "com.app.bundleid",
"sdk_version": "9.0.0"
}

And the callback JSON from the URL request provides us with the deep link URL:

URLSession.shared.dataTask(with: request) { data, response, error in
guard
let data = data,
let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let deepLink = dict["deepLink"] as? String,
let url = URL(string: deepLink)
else {
completion(nil)
return
}
// Handle your deep link URL here
}.resume()

Of course, on macOS, the options for handling opening URLs in your app are limited, so here is a quick workaround suggestion:

  1. Equip your marketing website to open a fallback URL based on your Deep Link URL if the user is on a macOS device. Here, the fallback URL will contain payload information like a user ID.
  2. Use JavaScript to handle the URL and prepare a custom URL scheme to be opened. A URL scheme is something that native apps running on macOS can handle, like my app://additional-information .
  3. Handle the URL scheme in-app, directly accessing the payload data and carrying about your business.

In conclusion, it’s important to remember that as developers, it’s up to us to solve our own problems and carve out a path for ourselves, and our peers.

Giving macOS Dynamic Links support once seemed difficult, but through patience and a little digging, we managed to overcome the problem.

Keeping a positive mindset about your learning and research ability as a developer is key for having the confidence to tackle difficult problems. So, stay patient and try your best! You’ll learn more that way anyways.

If you’ve been reading my articles, you’ll know I’m working on EasyFirebase, a Swift Package that makes Firebase much easier to use. Dynamic Links are supported for iOS and macOS in the package out-of-the-box, so you needn’t implement this all yourself if you use my package.

To get started, you can create links like this:

EasyLink.urlPrefix = "https://app.page.link"var link = EasyLink(host: "app.com", path: "/user", query: ("id", "ABCDEFG"))

Then shorten it like this:

link.shorten { url in
// ...
}

And handle it like this:

EasyLink.handle(url) { easyLink in
guard let easyLink = easyLink else { return }
if easyLink.path == "/user" {
if let id = easyLink.query["id"] {
// Handle user ID here.
}
}
}



News Credit

%d bloggers like this: