背景
仕事でDynamicLinksを用いてアプリを起動しようとしたら、パラメータは合っているはずなのになぜかパラメータをうまく受け取れなくてハマった...
今回はそのときどのような確認をして、どう解決したのかを備忘として残します。
Firebase Dynamic Linksとは
そもそもの説明を簡単にします。
例えば、Webでサービスを利用しているユーザにアプリ利用を促進させたいケースがあります。
その場合に利用されるのがFirebase Dynamic Links
です。
DynamicLinksはアプリのインストールの有無にかかわらず、複数のプラットフォームで機能するリンクです。DynamicLinksを開くと、ネイティブアプリのリンク先のコンテンツに直接移動します。
インストール済みであれば、アプリが起動してパラメータをもとに処理を定義することができます。
インストールしていなければ、iOSの場合はApple Storeのアプリページへの導線まで飛び、アプリのインストール・利用を促せます。
このDynamicLinksはFirebaseコンソールの方から簡単に設定できますし、バックエンドの方でリンクを動的に設定することもできます。
また、OS向けのパラメータも設定でき様々な設定ができます。
iOS向けパラメータの例
パラメータ | 説明 | 例 |
---|---|---|
ibi | リンクを開くために利用するアプリのバンドルID | jp.co.hogehoge |
imv | リンクを開けるアプリの最小バージョンの番号 | 1.0.0 |
今回のケースでは、後者のバックエンド側でリンクを作成する方法が該当します。
SDK内部の処理を追ってみた
Firebaseの公式ドキュメントでもあるように、アプリがインストール済みの場合にユニバーサルリンクとして受信されるリンクの処理があります。
func application(_ application: UIApplication, continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
let handled = DynamicLinks.dynamicLinks()
.handleUniversalLink(userActivity.webpageURL!) { dynamiclink, error in
// ...
}
return handled
}
しかし、DynamicLinksに設定するパラメータは正しいし、DynamicLinks内のディープリンクも間違えてはいないのに(本当に)、いくら試してもhandled
にfalse
しか返ってこず、DynamicLinksを受け取った時の処理が再現されない。。😢
そこでSDK内部の処理まで追ってみることに。
まずは、呼び出しているhandleUniversalLink
で何をしているのかを見てみました。
すると、canHandleUniversalLink
という定数がfalse
を返していることがわかり、深ぼってみるとcanParseUniversalLinkURL
がfalse
になっていることに気づきました。
- (BOOL)handleUniversalLink:(NSURL *)universalLinkURL
completion:(FIRDynamicLinkUniversalLinkHandler)completion {
if ([self matchesShortLinkFormat:universalLinkURL]) {
__weak __typeof__(self) weakSelf = self;
[self resolveShortLink:universalLinkURL
completion:^(NSURL *url, NSError *error) {
// 今回ここは関連しなかったので省略
return YES;
} else {
[self dynamicLinkFromUniversalLinkURL:universalLinkURL completion:completion];
BOOL canHandleUniversalLink =
// canParseUniversalLinkURLで異変が起きているぽいな👀
[self canParseUniversalLinkURL:universalLinkURL] && universalLinkURL.query.length > 0 &&
FIRDLDictionaryFromQuery(universalLinkURL.query)[kFIRDLParameterLink];
return canHandleUniversalLink;
}
}
そして、canHandleUniversalLink
の中にはFIRDLIsAValidDLWithFDLDomain
メソッドの記述しかなかったので、そこを辿ってみました。
BOOL FIRDLIsAValidDLWithFDLDomain(NSURL *_Nullable URL) {
BOOL matchesRegularExpression = false;
NSString *urlStr = URL.absoluteString;
if ([URL.host containsString:@".page.link"] || [URL.host containsString:@".app.goo.gl"] ||
[URL.host containsString:@".app.google"]) {
// このケースに来ているな👀
// Matches the *.page.link and *.app.goo.gl domains.
matchesRegularExpression =
([urlStr rangeOfString:
@"^https?://"
@"[a-zA-Z0-9]+((\\.app\\.goo\\.gl)|(\\.page\\.link)|(\\.app\\.google))((\\/"
@"?\\?.*link=https?.*)|(\\/[a-zA-Z0-9-_]+)((\\/?\\?.*=.*)?$|$))"
options:NSRegularExpressionSearch]
.location != NSNotFound);
// ↑↑↑ こいつが怪しいな👀🤔😠 ↑↑↑
if (!matchesRegularExpression) {
// こっちには落ちてきていない
}
}
return matchesRegularExpression;
}
matchesRegularExpression
の結果を返していることがわかったので、ここで定義されている正規表現が怪しいというところまでわかりました。
じゃあ実際、この正規表現はどうなっているのかをみていきましょう🕵️♂️
自分が検証していたリンクはhttps://hogehoge.page.link/?ibi=xxxxx&isi=xxxxx&link=https://hugahuga
みたいな形式になっていました。
page.link
までの正規表現に関してはひとまず問題はなさそうだったので、クエリパラメータが怪しそうです。
よく見てみると、なんかパラメータは?link
から始まるようにしないといけない条件になっていないか...
((\\/?\\?.*link=https?.*)|(\\/[a-zA-Z0-9-_]+)((\\/?\\?.*=.*)?$|$))
まさかと思い、パラメータの先頭が?link
から始まるように調整してもらったところ無事正常に動いた...
Firebaseのコンソール上から作成するDynamicLinksでは、長いダイナミックリンク
を見るとわかるように、ドメインの後はlink
のパラメータがつくようになっていたのでこういう仕様なんだなと受け入れるしかないようです...
※画像雑ですみません🙇♂️
まとめ
要するに、バックエンドで動的に作成するときにディープリンクを用いる場合はドメイン直後のパラメータの先頭はlinkから始まるようにしないといけないようです。
簡単に比較するとこんな感じ
🙆♂️ https://hogehoge.page.link/?link=https://hugahuga&ibi=xxxxx&isi=xxxxx
🙅♂️ https://hogehoge.page.link/?ibi=xxxxx&isi=xxxxx&link=https://hugahuga
パラメータは順不同であってほしい世界でした。
ちなみに、記事執筆時のfirebase-iOS-sdk
の最新バージョンは10.7.0
でしたが、この正規表現に変更はありませんでしたので、引き続きこの仕様であることを注意しないといけません。
この投稿が同様にハマっている他の方の助けになることを願っています。