From 5daaa5a32fccc8c0e5aef96e5694286a5c5b4402 Mon Sep 17 00:00:00 2001 From: Marcus Kida Date: Wed, 11 Jan 2023 15:12:07 +0100 Subject: [PATCH] feat(AppExtension): Improve open in link validation, add L10n --- Localization/app.json | 5 + Mastodon.xcodeproj/project.pbxproj | 20 + .../xcshareddata/swiftpm/Package.resolved | 492 +++++++++--------- .../Generated/Strings.swift | 6 + .../Resources/Base.lproj/Localizable.strings | 1 + OpenInActionExtension/Action.js | 11 +- .../ActionRequestHandler.swift | 53 +- 7 files changed, 329 insertions(+), 259 deletions(-) diff --git a/Localization/app.json b/Localization/app.json index dc758bb7b..0f471d973 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -805,5 +805,10 @@ "unfollow": "Unfollow" } } + }, + "extension": { + "open_in": { + "invalid_link_error": "This doesn't seem to be a valid Mastodon link." + } } } diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index a187164b5..4e03ca0c8 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ 2A71F543296DBDA80049F54A /* ActionRequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A71F53F296DBDA80049F54A /* ActionRequestHandler.swift */; }; 2A76F75C2930D94700B3388D /* HashtagTimelineHeaderViewActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A76F75B2930D94700B3388D /* HashtagTimelineHeaderViewActionButton.swift */; }; 2A82294F29262EE000D2A1F7 /* AppContext+NextAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A82294E29262EE000D2A1F7 /* AppContext+NextAccount.swift */; }; + 2A90A157296EEE500026C155 /* MastodonSDKDynamic in Frameworks */ = {isa = PBXBuildFile; productRef = 2A90A156296EEE500026C155 /* MastodonSDKDynamic */; }; 2AB12E4629362F27006BC925 /* DataSourceFacade+Translate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AB12E4529362F27006BC925 /* DataSourceFacade+Translate.swift */; }; 2AE244482927831100BDBF7C /* UIImage+SFSymbols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AE244472927831100BDBF7C /* UIImage+SFSymbols.swift */; }; 2D198643261BF09500F0B013 /* SearchResultItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D198642261BF09500F0B013 /* SearchResultItem.swift */; }; @@ -504,6 +505,16 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + 2A90A159296EEE500026C155 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; 9E44C7212967AD17004B2A72 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -1131,6 +1142,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 2A90A157296EEE500026C155 /* MastodonSDKDynamic in Frameworks */, 2A64515E29642A8A00CD8B8A /* UniformTypeIdentifiers.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2832,12 +2844,16 @@ 2A64515929642A8A00CD8B8A /* Sources */, 2A64515A29642A8A00CD8B8A /* Frameworks */, 2A64515B29642A8A00CD8B8A /* Resources */, + 2A90A159296EEE500026C155 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( ); name = OpenInActionExtension; + packageProductDependencies = ( + 2A90A156296EEE500026C155 /* MastodonSDKDynamic */, + ); productName = FollowActionExtension; productReference = 2A64515D29642A8A00CD8B8A /* OpenInActionExtension.appex */; productType = "com.apple.product-type.app-extension"; @@ -4948,6 +4964,10 @@ /* End XCConfigurationList section */ /* Begin XCSwiftPackageProductDependency section */ + 2A90A156296EEE500026C155 /* MastodonSDKDynamic */ = { + isa = XCSwiftPackageProductDependency; + productName = MastodonSDKDynamic; + }; 357FEEAE29523D470021C9DC /* MastodonSDKDynamic */ = { isa = XCSwiftPackageProductDependency; productName = MastodonSDKDynamic; diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved index d2dce549d..e029f5bed 100644 --- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,248 +1,250 @@ { - "pins" : [ - { - "identity" : "alamofire", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Alamofire/Alamofire.git", - "state" : { - "revision" : "8dd85aee02e39dd280c75eef88ffdb86eed4b07b", - "version" : "5.6.2" + "object": { + "pins": [ + { + "package": "Alamofire", + "repositoryURL": "https://github.com/Alamofire/Alamofire.git", + "state": { + "branch": null, + "revision": "8dd85aee02e39dd280c75eef88ffdb86eed4b07b", + "version": "5.6.2" + } + }, + { + "package": "AlamofireImage", + "repositoryURL": "https://github.com/Alamofire/AlamofireImage.git", + "state": { + "branch": null, + "revision": "98cbb00ce0ec5fc8e52a5b50a6bfc08d3e5aee10", + "version": "4.2.0" + } + }, + { + "package": "CommonOSLog", + "repositoryURL": "https://github.com/MainasuK/CommonOSLog", + "state": { + "branch": null, + "revision": "c121624a30698e9886efe38aebb36ff51c01b6c2", + "version": "0.1.1" + } + }, + { + "package": "FaviconFinder", + "repositoryURL": "https://github.com/will-lumley/FaviconFinder.git", + "state": { + "branch": null, + "revision": "1f74844f77f79b95c0bb0130b3a87d4f340e6d3a", + "version": "3.3.0" + } + }, + { + "package": "FLAnimatedImage", + "repositoryURL": "https://github.com/Flipboard/FLAnimatedImage.git", + "state": { + "branch": null, + "revision": "d4f07b6f164d53c1212c3e54d6460738b1981e9f", + "version": "1.0.17" + } + }, + { + "package": "FPSIndicator", + "repositoryURL": "https://github.com/MainasuK/FPSIndicator.git", + "state": { + "branch": null, + "revision": "e4a5067ccd5293b024c767f09e51056afd4a4796", + "version": "1.1.0" + } + }, + { + "package": "Fuzi", + "repositoryURL": "https://github.com/cezheng/Fuzi.git", + "state": { + "branch": null, + "revision": "f08c8323da21e985f3772610753bcfc652c2103f", + "version": "3.1.3" + } + }, + { + "package": "KeychainAccess", + "repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess.git", + "state": { + "branch": null, + "revision": "84e546727d66f1adc5439debad16270d0fdd04e7", + "version": "4.2.2" + } + }, + { + "package": "Kingfisher", + "repositoryURL": "https://github.com/onevcat/Kingfisher.git", + "state": { + "branch": null, + "revision": "44e891bdb61426a95e31492a67c7c0dfad1f87c5", + "version": "7.4.1" + } + }, + { + "package": "MetaTextKit", + "repositoryURL": "https://github.com/TwidereProject/MetaTextKit.git", + "state": { + "branch": null, + "revision": "dcd5255d6930c2fab408dc8562c577547e477624", + "version": "2.2.5" + } + }, + { + "package": "NextLevelSessionExporter", + "repositoryURL": "https://github.com/NextLevel/NextLevelSessionExporter.git", + "state": { + "branch": null, + "revision": "b6c0cce1aa37fe1547d694f958fac3c3524b74da", + "version": "0.4.6" + } + }, + { + "package": "Nuke", + "repositoryURL": "https://github.com/kean/Nuke.git", + "state": { + "branch": null, + "revision": "a002b7fd786f2df2ed4333fe73a9727499fd9d97", + "version": "10.11.2" + } + }, + { + "package": "NukeFLAnimatedImagePlugin", + "repositoryURL": "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git", + "state": { + "branch": null, + "revision": "b59c346a7d536336db3b0f12c72c6e53ee709e16", + "version": "8.0.0" + } + }, + { + "package": "Pageboy", + "repositoryURL": "https://github.com/uias/Pageboy", + "state": { + "branch": null, + "revision": "af8fa81788b893205e1ff42ddd88c5b0b315d7c5", + "version": "3.7.0" + } + }, + { + "package": "PanModal", + "repositoryURL": "https://github.com/slackhq/PanModal.git", + "state": { + "branch": null, + "revision": "b012aecb6b67a8e46369227f893c12544846613f", + "version": "1.2.7" + } + }, + { + "package": "SDWebImage", + "repositoryURL": "https://github.com/SDWebImage/SDWebImage.git", + "state": { + "branch": null, + "revision": "3312bf5e67b52fbce7c3caf431b0cda721a9f7bb", + "version": "5.14.2" + } + }, + { + "package": "Stripes", + "repositoryURL": "https://github.com/eneko/Stripes.git", + "state": { + "branch": null, + "revision": "d533fd44b8043a3abbf523e733599173d6f98c11", + "version": "0.2.0" + } + }, + { + "package": "swift-collections", + "repositoryURL": "https://github.com/apple/swift-collections.git", + "state": { + "branch": null, + "revision": "f504716c27d2e5d4144fa4794b12129301d17729", + "version": "1.0.3" + } + }, + { + "package": "swift-nio", + "repositoryURL": "https://github.com/apple/swift-nio.git", + "state": { + "branch": null, + "revision": "546610d52b19be3e19935e0880bb06b9c03f5cef", + "version": "1.14.4" + } + }, + { + "package": "swift-nio-zlib-support", + "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", + "state": { + "branch": null, + "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", + "version": "1.0.0" + } + }, + { + "package": "SwiftSoup", + "repositoryURL": "https://github.com/scinfu/SwiftSoup.git", + "state": { + "branch": null, + "revision": "6778575285177365cbad3e5b8a72f2a20583cfec", + "version": "2.4.3" + } + }, + { + "package": "Introspect", + "repositoryURL": "https://github.com/siteline/SwiftUI-Introspect.git", + "state": { + "branch": null, + "revision": "f2616860a41f9d9932da412a8978fec79c06fe24", + "version": "0.1.4" + } + }, + { + "package": "TabBarPager", + "repositoryURL": "https://github.com/TwidereProject/TabBarPager.git", + "state": { + "branch": null, + "revision": "488aa66d157a648901b61721212c0dec23d27ee5", + "version": "0.1.0" + } + }, + { + "package": "Tabman", + "repositoryURL": "https://github.com/uias/Tabman", + "state": { + "branch": null, + "revision": "4a4f7c755b875ffd4f9ef10d67a67883669d2465", + "version": "2.13.0" + } + }, + { + "package": "TOCropViewController", + "repositoryURL": "https://github.com/TimOliver/TOCropViewController.git", + "state": { + "branch": null, + "revision": "d0470491f56e734731bbf77991944c0dfdee3e0e", + "version": "2.6.1" + } + }, + { + "package": "UIHostingConfigurationBackport", + "repositoryURL": "https://github.com/woxtu/UIHostingConfigurationBackport.git", + "state": { + "branch": null, + "revision": "6091f2d38faa4b24fc2ca0389c651e2f666624a3", + "version": "0.1.0" + } + }, + { + "package": "UITextView+Placeholder", + "repositoryURL": "https://github.com/MainasuK/UITextView-Placeholder.git", + "state": { + "branch": null, + "revision": "20f513ded04a040cdf5467f0891849b1763ede3b", + "version": "1.4.1" + } } - }, - { - "identity" : "alamofireimage", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Alamofire/AlamofireImage.git", - "state" : { - "revision" : "98cbb00ce0ec5fc8e52a5b50a6bfc08d3e5aee10", - "version" : "4.2.0" - } - }, - { - "identity" : "commonoslog", - "kind" : "remoteSourceControl", - "location" : "https://github.com/MainasuK/CommonOSLog", - "state" : { - "revision" : "c121624a30698e9886efe38aebb36ff51c01b6c2", - "version" : "0.1.1" - } - }, - { - "identity" : "faviconfinder", - "kind" : "remoteSourceControl", - "location" : "https://github.com/will-lumley/FaviconFinder.git", - "state" : { - "revision" : "1f74844f77f79b95c0bb0130b3a87d4f340e6d3a", - "version" : "3.3.0" - } - }, - { - "identity" : "flanimatedimage", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Flipboard/FLAnimatedImage.git", - "state" : { - "revision" : "d4f07b6f164d53c1212c3e54d6460738b1981e9f", - "version" : "1.0.17" - } - }, - { - "identity" : "fpsindicator", - "kind" : "remoteSourceControl", - "location" : "https://github.com/MainasuK/FPSIndicator.git", - "state" : { - "revision" : "e4a5067ccd5293b024c767f09e51056afd4a4796", - "version" : "1.1.0" - } - }, - { - "identity" : "fuzi", - "kind" : "remoteSourceControl", - "location" : "https://github.com/cezheng/Fuzi.git", - "state" : { - "revision" : "f08c8323da21e985f3772610753bcfc652c2103f", - "version" : "3.1.3" - } - }, - { - "identity" : "keychainaccess", - "kind" : "remoteSourceControl", - "location" : "https://github.com/kishikawakatsumi/KeychainAccess.git", - "state" : { - "revision" : "84e546727d66f1adc5439debad16270d0fdd04e7", - "version" : "4.2.2" - } - }, - { - "identity" : "kingfisher", - "kind" : "remoteSourceControl", - "location" : "https://github.com/onevcat/Kingfisher.git", - "state" : { - "revision" : "44e891bdb61426a95e31492a67c7c0dfad1f87c5", - "version" : "7.4.1" - } - }, - { - "identity" : "metatextkit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/TwidereProject/MetaTextKit.git", - "state" : { - "revision" : "dcd5255d6930c2fab408dc8562c577547e477624", - "version" : "2.2.5" - } - }, - { - "identity" : "nextlevelsessionexporter", - "kind" : "remoteSourceControl", - "location" : "https://github.com/NextLevel/NextLevelSessionExporter.git", - "state" : { - "revision" : "b6c0cce1aa37fe1547d694f958fac3c3524b74da", - "version" : "0.4.6" - } - }, - { - "identity" : "nuke", - "kind" : "remoteSourceControl", - "location" : "https://github.com/kean/Nuke.git", - "state" : { - "revision" : "a002b7fd786f2df2ed4333fe73a9727499fd9d97", - "version" : "10.11.2" - } - }, - { - "identity" : "nuke-flanimatedimage-plugin", - "kind" : "remoteSourceControl", - "location" : "https://github.com/kean/Nuke-FLAnimatedImage-Plugin.git", - "state" : { - "revision" : "b59c346a7d536336db3b0f12c72c6e53ee709e16", - "version" : "8.0.0" - } - }, - { - "identity" : "pageboy", - "kind" : "remoteSourceControl", - "location" : "https://github.com/uias/Pageboy", - "state" : { - "revision" : "af8fa81788b893205e1ff42ddd88c5b0b315d7c5", - "version" : "3.7.0" - } - }, - { - "identity" : "panmodal", - "kind" : "remoteSourceControl", - "location" : "https://github.com/slackhq/PanModal.git", - "state" : { - "revision" : "b012aecb6b67a8e46369227f893c12544846613f", - "version" : "1.2.7" - } - }, - { - "identity" : "sdwebimage", - "kind" : "remoteSourceControl", - "location" : "https://github.com/SDWebImage/SDWebImage.git", - "state" : { - "revision" : "3312bf5e67b52fbce7c3caf431b0cda721a9f7bb", - "version" : "5.14.2" - } - }, - { - "identity" : "stripes", - "kind" : "remoteSourceControl", - "location" : "https://github.com/eneko/Stripes.git", - "state" : { - "revision" : "d533fd44b8043a3abbf523e733599173d6f98c11", - "version" : "0.2.0" - } - }, - { - "identity" : "swift-collections", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-collections.git", - "state" : { - "revision" : "f504716c27d2e5d4144fa4794b12129301d17729", - "version" : "1.0.3" - } - }, - { - "identity" : "swift-nio", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio.git", - "state" : { - "revision" : "546610d52b19be3e19935e0880bb06b9c03f5cef", - "version" : "1.14.4" - } - }, - { - "identity" : "swift-nio-zlib-support", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio-zlib-support.git", - "state" : { - "revision" : "37760e9a52030bb9011972c5213c3350fa9d41fd", - "version" : "1.0.0" - } - }, - { - "identity" : "swiftsoup", - "kind" : "remoteSourceControl", - "location" : "https://github.com/scinfu/SwiftSoup.git", - "state" : { - "revision" : "6778575285177365cbad3e5b8a72f2a20583cfec", - "version" : "2.4.3" - } - }, - { - "identity" : "swiftui-introspect", - "kind" : "remoteSourceControl", - "location" : "https://github.com/siteline/SwiftUI-Introspect.git", - "state" : { - "revision" : "f2616860a41f9d9932da412a8978fec79c06fe24", - "version" : "0.1.4" - } - }, - { - "identity" : "tabbarpager", - "kind" : "remoteSourceControl", - "location" : "https://github.com/TwidereProject/TabBarPager.git", - "state" : { - "revision" : "488aa66d157a648901b61721212c0dec23d27ee5", - "version" : "0.1.0" - } - }, - { - "identity" : "tabman", - "kind" : "remoteSourceControl", - "location" : "https://github.com/uias/Tabman", - "state" : { - "revision" : "4a4f7c755b875ffd4f9ef10d67a67883669d2465", - "version" : "2.13.0" - } - }, - { - "identity" : "tocropviewcontroller", - "kind" : "remoteSourceControl", - "location" : "https://github.com/TimOliver/TOCropViewController.git", - "state" : { - "revision" : "d0470491f56e734731bbf77991944c0dfdee3e0e", - "version" : "2.6.1" - } - }, - { - "identity" : "uihostingconfigurationbackport", - "kind" : "remoteSourceControl", - "location" : "https://github.com/woxtu/UIHostingConfigurationBackport.git", - "state" : { - "revision" : "6091f2d38faa4b24fc2ca0389c651e2f666624a3", - "version" : "0.1.0" - } - }, - { - "identity" : "uitextview-placeholder", - "kind" : "remoteSourceControl", - "location" : "https://github.com/MainasuK/UITextView-Placeholder.git", - "state" : { - "revision" : "20f513ded04a040cdf5467f0891849b1763ede3b", - "version" : "1.4.1" - } - } - ], - "version" : 2 + ] + }, + "version": 1 } diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index 9034cf0c5..fb516496a 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -469,6 +469,12 @@ public enum L10n { } } } + public enum Extension { + public enum OpenIn { + /// This doesn't seem to be a valid Mastodon link. + public static let invalidLinkError = L10n.tr("Localizable", "Extension.OpenIn.InvalidLinkError", fallback: "This doesn't seem to be a valid Mastodon link.") + } + } public enum Scene { public enum AccountList { /// Add Account diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings index acafb44ab..2c2d295b3 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings @@ -513,3 +513,4 @@ You can’t go wrong with any of our recommend servers, so regardless of which o "Scene.Privacy.Button.confirm" = "I agree"; "Scene.Privacy.Policy.Ios" = "Privacy Policy - Mastodon for iOS"; "Scene.Privacy.Policy.Server" = "Privacy Policy - %@"; +"Extension.OpenIn.InvalidLinkError" = "This doesn't seem to be a valid Mastodon link."; diff --git a/OpenInActionExtension/Action.js b/OpenInActionExtension/Action.js index e86c5d255..b425558b6 100644 --- a/OpenInActionExtension/Action.js +++ b/OpenInActionExtension/Action.js @@ -19,7 +19,12 @@ Action.prototype = { }, finalize: function(arguments) { - window.location = arguments["openURL"] + let alertMessage = arguments["alert"] + if (alertMessage) { + alert(alertMessage) + } else { + window.location = arguments["openURL"] + } } }; @@ -27,12 +32,12 @@ Action.prototype = { function detectUsername() { var uriUsername = document.documentURI.match("@(.+)@([a-z0-9]+\.[a-z0-9]+)") - if (typeof uriUsername === "Array") { + if (Array.isArray(uriUsername)) { return uriUsername[0] } var querySelector = document.head.querySelector('[property="profile:username"]') - if (typeof querySelector === "Object") { + if (querySelector !== null && typeof querySelector === "object") { return querySelector.content } diff --git a/OpenInActionExtension/ActionRequestHandler.swift b/OpenInActionExtension/ActionRequestHandler.swift index 15c51cc37..fdc73f03a 100644 --- a/OpenInActionExtension/ActionRequestHandler.swift +++ b/OpenInActionExtension/ActionRequestHandler.swift @@ -5,13 +5,16 @@ // Created by Marcus Kida on 03.01.23. // +import Combine import UIKit import MobileCoreServices import UniformTypeIdentifiers +import MastodonSDK +import MastodonLocalization class ActionRequestHandler: NSObject, NSExtensionRequestHandling { - var extensionContext: NSExtensionContext? + var cancellables = [AnyCancellable]() func beginRequest(with context: NSExtensionContext) { // Do not call super in an Action extension with no user interface @@ -28,7 +31,7 @@ class ActionRequestHandler: NSObject, NSExtensionRequestHandling { .first guard let itemProvider = itemProvider else { - return doneWithResults(nil) + return doneWithInvalidLink() } itemProvider.loadItem(forTypeIdentifier: UTType.propertyList.identifier, options: nil, completionHandler: { [weak self] item, error in @@ -37,16 +40,16 @@ class ActionRequestHandler: NSObject, NSExtensionRequestHandling { let dictionary = item as? NSDictionary, let results = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as? NSDictionary else { - self?.doneWithResults(nil) + self?.doneWithInvalidLink() return } if let username = results["username"] as? String { self?.completeWithOpenUserProfile(username) } else if let url = results["url"] as? String { - self?.completeWithSearch(url) + self?.continueWithSearch(url) } else { - self?.doneWithResults(nil) + self?.doneWithInvalidLink() } } }) @@ -60,13 +63,41 @@ private extension ActionRequestHandler { ]) } - func completeWithSearch(_ query: String) { - guard let query = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { - return doneWithResults(nil) + func continueWithSearch(_ query: String) { + guard + let url = URL(string: query), + let host = url.host + else { + return doneWithInvalidLink() } - doneWithResults( - ["openURL": "mastodon://search?query=\(query)"] - ) + + Mastodon.API + .Instance + .instance( + session: .shared, + domain: host + ) + .receive(on: DispatchQueue.main) + .sink { _ in + // no-op + } receiveValue: { [weak self] response in + guard response.value.version != nil else { + self?.doneWithInvalidLink() + return + } + guard let query = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { + self?.doneWithInvalidLink() + return + } + self?.doneWithResults( + ["openURL": "mastodon://search?query=\(query)"] + ) + } + .store(in: &cancellables) + } + + func doneWithInvalidLink() { + doneWithResults(["alert": L10n.Extension.OpenIn.invalidLinkError]) } func doneWithResults(_ resultsForJavaScriptFinalizeArg: [String: Any]?) {