123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 |
- //
- // ActionRequestHandler.swift
- // OpenInActionExtension
- //
- // Created by Marcus Kida on 03.01.23.
- //
- import Combine
- import UIKit
- import MobileCoreServices
- import UniformTypeIdentifiers
- import MastodonCore
- import MastodonSDK
- import MastodonLocalization
- class ActionRequestHandler: NSObject, NSExtensionRequestHandling {
- var extensionContext: NSExtensionContext?
- var cancellables = [AnyCancellable]()
-
- /// Capturing a static shared instance of AppContext here as otherwise there
- /// will be lifecycle issues and we don't want to keep multiple AppContexts around
- /// in case there another Action Extension process is spawned
- private static let appContext = AppContext()
-
- func beginRequest(with context: NSExtensionContext) {
- // Do not call super in an Action extension with no user interface
- self.extensionContext = context
-
- let itemProvider = context.inputItems
- .compactMap({ $0 as? NSExtensionItem })
- .reduce([NSItemProvider](), { partialResult, acc in
- var nextResult = partialResult
- nextResult += acc.attachments ?? []
- return nextResult
- })
- .filter({ $0.hasItemConformingToTypeIdentifier(UTType.propertyList.identifier) })
- .first
-
- guard let itemProvider = itemProvider else {
- return doneWithInvalidLink()
- }
-
- itemProvider.loadItem(forTypeIdentifier: UTType.propertyList.identifier, options: nil, completionHandler: { [weak self] item, error in
- DispatchQueue.main.async {
- guard
- let dictionary = item as? NSDictionary,
- let results = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as? NSDictionary
- else {
- self?.doneWithInvalidLink()
- return
- }
-
- if let url = results["url"] as? String {
- self?.performSearch(for: url)
- } else {
- self?.doneWithInvalidLink()
- }
- }
- })
- }
- }
- // Search API
- private extension ActionRequestHandler {
- func performSearch(for url: String) {
- guard
- let activeAuthenticationBox = Self.appContext
- .authenticationService
- .mastodonAuthenticationBoxes
- .first
- else {
- return doneWithResults(nil)
- }
-
- Mastodon.API
- .V2
- .Search
- .search(
- session: .shared,
- domain: activeAuthenticationBox.domain,
- query: .init(q: url, resolve: true),
- authorization: activeAuthenticationBox.userAuthorization
- )
- .receive(on: DispatchQueue.main)
- .sink { completion in
- // no-op
- } receiveValue: { [weak self] result in
- let value = result.value
- if let foundAccount = value.accounts.first {
- self?.doneWithResults([
- "openURL": "mastodon://profile/\(foundAccount.acct)"
- ])
- } else if let foundStatus = value.statuses.first {
- self?.doneWithResults([
- "openURL": "mastodon://status/\(foundStatus.id)"
- ])
- } else if let foundHashtag = value.hashtags.first {
- self?.continueWithSearch(foundHashtag.name)
- } else {
- self?.continueWithSearch(url)
- }
- }
- .store(in: &cancellables)
- }
- }
- // Fallback to In-App Search
- private extension ActionRequestHandler {
- func continueWithSearch(_ query: String) {
- guard
- let url = URL(string: query),
- let host = url.host
- else {
- return doneWithInvalidLink()
- }
-
- 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)
- }
- }
- // Action response handling
- private extension ActionRequestHandler {
- func doneWithInvalidLink() {
- doneWithResults(["alert": L10n.Extension.OpenIn.invalidLinkError])
- }
-
- func doneWithResults(_ resultsForJavaScriptFinalizeArg: [String: Any]?) {
- if let resultsForJavaScriptFinalize = resultsForJavaScriptFinalizeArg {
- let resultsDictionary = [NSExtensionJavaScriptFinalizeArgumentKey: resultsForJavaScriptFinalize]
- let resultsProvider = NSItemProvider(item: resultsDictionary as NSDictionary, typeIdentifier: UTType.propertyList.identifier)
- let resultsItem = NSExtensionItem()
- resultsItem.attachments = [resultsProvider]
- self.extensionContext!.completeRequest(returningItems: [resultsItem], completionHandler: nil)
- } else {
- self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
- }
- self.extensionContext = nil
- }
- }
|