95 lines
3.2 KiB
Swift
95 lines
3.2 KiB
Swift
// Copyright 2023-present 650 Industries. All rights reserved.
|
|
|
|
import ExpoModulesCore
|
|
import CoreSpotlight
|
|
|
|
/// Represents the Info.plist.
|
|
public struct InfoPlist {
|
|
public init() {}
|
|
|
|
/// Returns the custom URL schemes registered by the app ('CFBundleURLSchemes' array).
|
|
public static func bundleURLSchemes() -> [String] {
|
|
guard let path = Bundle.main.path(forResource: "Info", ofType: "plist") else {
|
|
log.error("Can't find path to Info.plist in the main bundle.")
|
|
return []
|
|
}
|
|
guard
|
|
// swiftlint:disable:next legacy_objc_type
|
|
let infoDict = NSDictionary(contentsOfFile: path) as? [String: AnyObject],
|
|
let anyDictionary = (infoDict["CFBundleURLTypes"] as? [[String: Any]])?.first,
|
|
let urlSchemes = anyDictionary["CFBundleURLSchemes"] as? [String]
|
|
else {
|
|
log.error("Can't find path to CFBundleURLSchemes in the Info.plist.")
|
|
return []
|
|
}
|
|
return urlSchemes
|
|
}
|
|
}
|
|
|
|
func encoded(_ value: String) -> String {
|
|
return value.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? value
|
|
}
|
|
|
|
func sendFakeDeepLinkEventToReactNative(obj: Any, url: String) {
|
|
NotificationCenter.default.post(
|
|
// swiftlint:disable:next legacy_objc_type
|
|
name: NSNotification.Name(rawValue: "RCTOpenURLNotification"),
|
|
object: obj,
|
|
userInfo: ["url": url])
|
|
}
|
|
|
|
func userInfoToQueryString(_ userInfo: [String: NSSecureCoding]?) -> String {
|
|
guard let userInfo = userInfo else {
|
|
return ""
|
|
}
|
|
var queryString = ""
|
|
for (key, value) in userInfo {
|
|
if let value = value as? String {
|
|
if key != "href" {
|
|
queryString += "&\(encoded(key))=\(encoded(value))"
|
|
}
|
|
}
|
|
}
|
|
return queryString
|
|
}
|
|
|
|
func prefixDeepLink(fragment: String) -> String {
|
|
// This can happen when an NSUserActivity href is used to activate the app.
|
|
if fragment.starts(with: "/") {
|
|
let schemes = InfoPlist.bundleURLSchemes()
|
|
return "\(schemes[0]):/\(fragment)"
|
|
}
|
|
return fragment
|
|
}
|
|
|
|
public class ExpoHeadAppDelegateSubscriber: ExpoAppDelegateSubscriber {
|
|
public func application(
|
|
_ application: UIApplication,
|
|
continue userActivity: NSUserActivity,
|
|
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
|
|
) -> Bool {
|
|
launchedActivity = userActivity
|
|
|
|
if let wellKnownHref = userActivity.userInfo?["href"] as? String {
|
|
// From a stored NSUserActivity, e.g. Quick Note or Siri Reminder
|
|
// From other native device to app
|
|
sendFakeDeepLinkEventToReactNative(obj: self, url: prefixDeepLink(fragment: wellKnownHref))
|
|
} else if userActivity.activityType == CSQueryContinuationActionType {
|
|
// From Spotlight search
|
|
if let query = userActivity.userInfo?[CSSearchQueryString] as? String {
|
|
let schemes = InfoPlist.bundleURLSchemes()
|
|
let encodedQuery = query.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? query
|
|
// swiftlint:disable:next todo
|
|
// TODO(EvanBacon): Allow user to define the scheme using structured data or something.
|
|
// opensearch = Chrome. spotlight = custom thing we're using to identify iOS
|
|
let url = "\(schemes[0])://search?q=\(encodedQuery)&ref=spotlight"
|
|
|
|
// https://github.com/search?q=
|
|
sendFakeDeepLinkEventToReactNative(obj: self, url: url)
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
}
|