shannon 54d04aed4e Continue to make MastodonAuthenticationBox the standard form of passing user information
Further simplify the AuthenticationViewModel and the public interface of AuthenticationServiceProvider.

Publish currentActiveUser from AuthenticationServiceProvider instead of assuming all callers know to take the first in the list as the active one.

contributes to iOS-319
2024-11-19 12:09:55 -05:00

164 lines
5.7 KiB
Swift

//
// 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]()
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 {
Task {
await self?.performSearch(for: url)
}
} else {
self?.doneWithInvalidLink()
}
}
})
}
}
// Search API
private extension ActionRequestHandler {
func performSearch(for url: String) async {
guard
let activeAuthenticationBox = await AuthenticationServiceProvider.shared.currentActiveUser.value
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 {
Task { [weak self] in
await self?.continueWithSearch(foundHashtag.name)
}
} else {
Task { [weak self] in
await self?.continueWithSearch(url)
}
}
}
.store(in: &cancellables)
}
}
// Fallback to In-App Search
private extension ActionRequestHandler {
func continueWithSearch(_ query: String) async {
guard
let url = URL(string: query),
let host = url.host,
let activeAuthenticationBox = await AuthenticationServiceProvider.shared.currentActiveUser.value
else {
return doneWithInvalidLink()
}
Mastodon.API
.Instance
.instance(
session: .shared,
authorization: activeAuthenticationBox.userAuthorization,
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
}
}