105 lines
3.2 KiB
Swift
105 lines
3.2 KiB
Swift
//
|
|
// ActionRequestHandler.swift
|
|
// IceCubesActionExtension
|
|
//
|
|
// Created by Thomas Durand on 26/01/2023.
|
|
//
|
|
|
|
import MobileCoreServices
|
|
import UIKit
|
|
import UniformTypeIdentifiers
|
|
|
|
import Models
|
|
import Network
|
|
|
|
// Sample code was sending this from a thread to another, let asume @Sendable for this
|
|
extension NSExtensionContext: @unchecked Sendable {}
|
|
|
|
final class ActionRequestHandler: NSObject, NSExtensionRequestHandling, Sendable {
|
|
enum Error: Swift.Error {
|
|
case inputProviderNotFound
|
|
case loadedItemHasWrongType
|
|
case urlNotFound
|
|
case noHost
|
|
case notMastodonInstance
|
|
}
|
|
|
|
func beginRequest(with context: NSExtensionContext) {
|
|
// Do not call super in an Action extension with no user interface
|
|
Task {
|
|
do {
|
|
let url = try await url(from: context)
|
|
guard await url.isMastodonInstance else {
|
|
throw Error.notMastodonInstance
|
|
}
|
|
await MainActor.run {
|
|
let deeplink = url.iceCubesAppDeepLink
|
|
let output = output(wrapping: deeplink)
|
|
context.completeRequest(returningItems: output)
|
|
}
|
|
} catch {
|
|
await MainActor.run {
|
|
context.completeRequest(returningItems: [])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension URL {
|
|
var isMastodonInstance: Bool {
|
|
get async {
|
|
do {
|
|
guard let host = host() else {
|
|
throw ActionRequestHandler.Error.noHost
|
|
}
|
|
let _: Instance = try await Client(server: host).get(endpoint: Instances.instance)
|
|
return true
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
var iceCubesAppDeepLink: URL {
|
|
var components = URLComponents(url: self, resolvingAgainstBaseURL: false)!
|
|
components.scheme = AppInfo.scheme.trimmingCharacters(in: [":", "/"])
|
|
return components.url!
|
|
}
|
|
}
|
|
|
|
extension ActionRequestHandler {
|
|
/// Will look for an input item that might provide the property list that Javascript sent us
|
|
private func url(from context: NSExtensionContext) async throws -> URL {
|
|
for item in context.inputItems as! [NSExtensionItem] {
|
|
guard let attachments = item.attachments else {
|
|
continue
|
|
}
|
|
for itemProvider in attachments {
|
|
guard itemProvider.hasItemConformingToTypeIdentifier(UTType.propertyList.identifier) else {
|
|
continue
|
|
}
|
|
guard let dictionary = try await itemProvider.loadItem(forTypeIdentifier: UTType.propertyList.identifier) as? [String: Any] else {
|
|
throw Error.loadedItemHasWrongType
|
|
}
|
|
let input = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as! [String: Any]? ?? [:]
|
|
guard let absoluteStringUrl = input["url"] as? String, let url = URL(string: absoluteStringUrl) else {
|
|
throw Error.urlNotFound
|
|
}
|
|
return url
|
|
}
|
|
}
|
|
throw Error.inputProviderNotFound
|
|
}
|
|
|
|
/// Wrap the output to the expected object so we send back results to JS
|
|
private func output(wrapping deeplink: URL) -> [NSExtensionItem] {
|
|
let results = ["deeplink": deeplink.absoluteString]
|
|
let dictionary = [NSExtensionJavaScriptFinalizeArgumentKey: results]
|
|
let provider = NSItemProvider(item: dictionary as NSDictionary, typeIdentifier: UTType.propertyList.identifier)
|
|
let item = NSExtensionItem()
|
|
item.attachments = [provider]
|
|
return [item]
|
|
}
|
|
}
|