Improve "Open in Mastodon" by using Search API (#888)

* feat(ActionExtension): Improve "Open in Mastodon" by using Search aPI

* Add code comment
This commit is contained in:
Marcus Kida 2023-01-16 23:36:00 +01:00 committed by GitHub
parent 8e7d115944
commit 3ec9e603df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 78 additions and 32 deletions

View File

@ -573,6 +573,7 @@
27D701F4292FC2D60031BCBB /* DataSourceFacade+URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+URL.swift"; sourceTree = "<group>"; };
2A1FE47B2938BB2600784BF1 /* FollowedTagsViewModel+DiffableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FollowedTagsViewModel+DiffableDataSource.swift"; sourceTree = "<group>"; };
2A1FE47D2938C11200784BF1 /* Collection+IsNotEmpty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+IsNotEmpty.swift"; sourceTree = "<group>"; };
2A33625329759B4200481A90 /* OpenInActionExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = OpenInActionExtension.entitlements; sourceTree = "<group>"; };
2A3F6FE2292ECB5E002E6DA7 /* FollowedTagsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedTagsViewModel.swift; sourceTree = "<group>"; };
2A3F6FE4292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedTagsTableViewCell.swift; sourceTree = "<group>"; };
2A506CF3292CD85800059C37 /* FollowedTagsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedTagsViewController.swift; sourceTree = "<group>"; };
@ -1333,6 +1334,7 @@
2A71F53C296DBDA80049F54A /* OpenInActionExtension */ = {
isa = PBXGroup;
children = (
2A33625329759B4200481A90 /* OpenInActionExtension.entitlements */,
2A71F53D296DBDA80049F54A /* Media.xcassets */,
2A71F53E296DBDA80049F54A /* Action.js */,
2A71F53F296DBDA80049F54A /* ActionRequestHandler.swift */,
@ -3939,6 +3941,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = Icon;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = OpenInActionExtension/OpenInActionExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
@ -3969,6 +3972,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = Icon;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = OpenInActionExtension/OpenInActionExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
@ -3998,6 +4002,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = Icon;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = OpenInActionExtension/OpenInActionExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
@ -4027,6 +4032,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = Icon;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = OpenInActionExtension/OpenInActionExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;

View File

@ -8,40 +8,23 @@
var Action = function() {};
Action.prototype = {
run: function(arguments) {
var payload = {
"username": detectUsername(),
"url": document.documentURI
}
arguments.completionFunction(payload)
},
finalize: function(arguments) {
let alertMessage = arguments["alert"]
const alertMessage = arguments["alert"]
const openURL = arguments["openURL"]
if (alertMessage) {
alert(alertMessage)
} else {
window.location = arguments["openURL"]
} else if (openURL) {
window.location = openURL
}
}
};
function detectUsername() {
var uriUsername = document.documentURI.match("(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+))")
if (Array.isArray(uriUsername)) {
return uriUsername[0]
}
var querySelector = document.head.querySelector('[property="profile:username"]')
if (querySelector !== null && typeof querySelector === "object") {
return querySelector.content
}
return undefined
}
var ExtensionPreprocessingJS = new Action

View File

@ -9,6 +9,7 @@ import Combine
import UIKit
import MobileCoreServices
import UniformTypeIdentifiers
import MastodonCore
import MastodonSDK
import MastodonLocalization
@ -16,6 +17,11 @@ 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
@ -44,10 +50,8 @@ class ActionRequestHandler: NSObject, NSExtensionRequestHandling {
return
}
if let username = results["username"] as? String {
self?.completeWithOpenUserProfile(username)
} else if let url = results["url"] as? String {
self?.continueWithSearch(url)
if let url = results["url"] as? String {
self?.performSearch(for: url)
} else {
self?.doneWithInvalidLink()
}
@ -56,13 +60,53 @@ class ActionRequestHandler: NSObject, NSExtensionRequestHandling {
}
}
// Search API
private extension ActionRequestHandler {
func completeWithOpenUserProfile(_ username: String) {
doneWithResults([
"openURL": "mastodon://profile/\(username)"
])
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),
@ -95,7 +139,10 @@ private extension ActionRequestHandler {
}
.store(in: &cancellables)
}
}
// Action response handling
private extension ActionRequestHandler {
func doneWithInvalidLink() {
doneWithResults(["alert": L10n.Extension.OpenIn.invalidLinkError])
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.org.joinmastodon.app</string>
</array>
</dict>
</plist>