Skip to content

Commit

Permalink
Added support for url rewriting. (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
grigorye authored Dec 4, 2023
2 parents be6d86c + 616dbb3 commit f30ff95
Show file tree
Hide file tree
Showing 14 changed files with 191 additions and 118 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
#! /bin/sh -x
#! /bin/bash

# This script defines the rules/acts like a matcher between an url and the corresponding application that is used to open the url.
# Feel free to modify it as necessary/replace it with something more appropriate.

set -eu -o pipefail
set -x
set -euo pipefail

url="$1"; shift
url="$1"
shift

v1Resolver=~/Library/Application\ Scripts/com.grigorye.URLHelperApp/AppBundleIdentifierForURL

if test -f "$v1Resolver"; then
appBundleIdentifier=$("$v1Resolver" "$url")
echo '{"finalURL": "'"$url"'", "appBundleIdentifier": "'"$appBundleIdentifier"'" }'
exit
fi

# Process redirects before making the "routing" decision.
redirectedURL=$(curl -Ls -o /dev/null -w '%{url_effective}' "$url" || echo "$url")
Expand All @@ -16,6 +26,6 @@ case "$redirectedURL" in
# echo "org.epichrome.app.Coding"
# ;;
*)
echo 'com.google.Chrome'
echo '{"finalURL": "'"$redirectedURL"'", "appBundleIdentifier": "com.google.Chrome" }'
;;
esac
14 changes: 9 additions & 5 deletions URLHelperApp/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
import Cocoa
import os.log

private let log = Logger(subsystem: "AppDelegate", category: "")
private let log = Logger(category: "AppDelegate")

private let urlToAppMapper: URLToAppMapper = ScriptBasedURLToAppMapper()
private let urlResolver: URLResolver = ScriptBasedURLResolver()

@NSApplicationMain
class AppDelegate : NSObject, NSApplicationDelegate {
Expand Down Expand Up @@ -40,13 +40,17 @@ class AppDelegate : NSObject, NSApplicationDelegate {
}

private func resolve(_ urls: [URL]) async throws -> [String: [URL]] {
try await withThrowingTaskGroup(of: (url: URL, appBundleIdentifier: String).self) { group in
try await withThrowingTaskGroup(of: (url: URL, appBundleIdentifier: String)?.self) { group in
for url in urls {
group.addTask {
try await (url, urlToAppMapper.appBundleIdentifierFor(url))
guard let resolution = try await urlResolver.resolveURL(url) else {
log.error("Unable to resolve \(url).")
return nil
}
return (resolution.finalURL, resolution.appBundleIdentifier)
}
}
return try await group.reduce([:]) { acc, x in
return try await group.compactMap { $0 }.reduce([:]) { acc, x in
var acc = acc
acc[x.appBundleIdentifier, default: []] += [x.url]
return acc
Expand Down
30 changes: 4 additions & 26 deletions URLHelperApp/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
Expand Down Expand Up @@ -49,6 +33,8 @@
</array>
</dict>
</array>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleURLTypes</key>
<array>
<dict>
Expand All @@ -65,17 +51,9 @@
<true/>
</dict>
</array>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSBackgroundOnly</key>
<true/>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2018 Grigory Entin. All rights reserved.</string>
<key>NSMainStoryboardFile</key>
<string>Main</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>
17 changes: 17 additions & 0 deletions URLHelperApp/Logging.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// Logging.swift
// URLHelperApp
//
// Created by Grigory Entin on 02/12/2023.
//

import Foundation
import os.log

private let logSubsystem = Bundle.main.bundleIdentifier!

extension Logger {
init(category: String) {
self.init(subsystem: logSubsystem, category: category)
}
}
2 changes: 1 addition & 1 deletion URLHelperApp/OutputFromLaunching.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation
import os.log

private let log = Logger(subsystem: "OutputFromLaunching", category: "")
private let log = Logger(category: "OutputFromLaunching")

func outputFromLaunching(executableURL: URL, arguments: [String]) async throws -> Data {
log.info("Executing \(executableURL.standardizedFileURL.path) with \(arguments).")
Expand Down
28 changes: 28 additions & 0 deletions URLHelperApp/SampleURLResolver.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// SampleURLResolver.swift
// URLHelperApp
//
// Created by Grigory Entin on 06/10/2018.
// Copyright © 2018 Grigory Entin. All rights reserved.
//

import Foundation

extension URL {

fileprivate func matchingURLResolution() -> URLResolution {
switch self {
case _ where absoluteString.hasPrefix("https://stackoverflow.com/"):
return URLResolution(finalURL: self, appBundleIdentifier: "org.epichrome.app.Coding")
default:
return URLResolution(finalURL: self, appBundleIdentifier: "com.google.Chrome")
}
}
}

class SampleURLResolver : URLResolver {

func resolveURL(_ url: URL) async throws -> URLResolution? {
url.matchingURLResolution()
}
}
31 changes: 0 additions & 31 deletions URLHelperApp/SampleURLToAppMapper.swift

This file was deleted.

62 changes: 62 additions & 0 deletions URLHelperApp/ScriptBasedURLResolver.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// ScriptBasedURLResolver.swift
// URLHelperApp
//
// Created by Grigory Entin on 05/10/2018.
// Copyright © 2018 Grigory Entin. All rights reserved.
//

import Foundation

class ScriptBasedURLResolver : URLResolver {

let fileManager = FileManager()
let resolverScriptName = "AppBundleIdentifierAndURLForURL"

var defaultResolverURL: URL {
let scriptsDirectoryURL: URL = try! fileManager.url(for: .applicationScriptsDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let resolverURL = scriptsDirectoryURL.appendingPathComponent(resolverScriptName)
return resolverURL
}

func preprocessResolverURL() async throws -> URL? {
try await makeSureResolverScriptExists(resolverURL: defaultResolverURL)
}

func makeSureResolverScriptExists(resolverURL: URL) async throws -> URL? {
do {
try fileManager.copyItem(at: bundledResolverURL, to: resolverURL)
return resolverURL
} catch {
switch error {
case CocoaError.fileWriteFileExists:
return resolverURL
case CocoaError.fileWriteNoPermission:
guard let updatedResolverURL = try await facilitateWriteAccessForURLResolverScript(at: resolverURL) else {
return nil
}
return try await makeSureResolverScriptExists(resolverURL: updatedResolverURL)
default:
throw error
}
}
}

var bundledResolverURL: URL {
let bundle = Bundle(for: type(of: self))
return bundle.url(forResource: resolverScriptName, withExtension: "")!
}

func resolveURL(_ url: URL) async throws -> URLResolution? {
guard let resolverURL = try await preprocessResolverURL() else {
return nil
}
return try await resolveURLWithoutPreprocessing(url, resolverURL: resolverURL)
}

private func resolveURLWithoutPreprocessing(_ url: URL, resolverURL: URL) async throws -> URLResolution {
let data = try await outputFromLaunching(executableURL: resolverURL, arguments: [url.absoluteString])
let resolution = try JSONDecoder().decode(URLResolution.self, from: data)
return resolution
}
}
37 changes: 0 additions & 37 deletions URLHelperApp/ScriptBasedURLToAppMapper.swift

This file was deleted.

2 changes: 2 additions & 0 deletions URLHelperApp/URLHelperApp.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
Expand Down
18 changes: 18 additions & 0 deletions URLHelperApp/URLResolver.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// URLResolver.swift
// URLHelperApp
//
// Created by Grigory Entin on 04/10/2018.
// Copyright © 2018 Grigory Entin. All rights reserved.
//

import Foundation

struct URLResolution: Decodable {
let finalURL: URL
let appBundleIdentifier: String
}

protocol URLResolver {
func resolveURL(_ url: URL) async throws -> URLResolution?
}
13 changes: 0 additions & 13 deletions URLHelperApp/URLToAppMapper.swift

This file was deleted.

32 changes: 32 additions & 0 deletions URLHelperApp/WriteAccessFacilitator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// WriteAccessFacilitator.swift
// URLHelperApp
//
// Created by Grigory Entin on 01/12/2023.
//

import AppKit
import Foundation

private let fileManager = FileManager()

private var appName: String {
Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? fileManager.displayName(atPath: Bundle.main.bundlePath)
}

func facilitateWriteAccessForURLResolverScript(at url: URL) async throws -> URL? {
try await facilitateWriteAccessViaUserInteraction(to: url, message: String(localized: "Select the location for the resolver script for \(appName)"))
}

@MainActor
func facilitateWriteAccessViaUserInteraction(to url: URL, message: String) async throws -> URL? {
let panel = {
$0.message = message
$0.directoryURL = url.deletingLastPathComponent()
$0.nameFieldStringValue = url.lastPathComponent
$0.prompt = String(localized: "Select")
return $0
}(NSSavePanel())

return panel.url
}
5 changes: 4 additions & 1 deletion project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ targets:
deploymentTarget: '12.0'
sources:
- path: URLHelperApp
- path: URLHelperApp/AppBundleIdentifierForURL
- path: URLHelperApp/AppBundleIdentifierAndURLForURL
buildPhase: resources
settings:
CODE_SIGN_ENTITLEMENTS: URLHelperApp/URLHelperApp.entitlements
INFOPLIST_PREPROCESS: YES
INFOPLIST_OTHER_PREPROCESSOR_FLAGS: -CC
INFOPLIST_KEY_LSApplicationCategoryType: public.app-category.utilities
INFOPLIST_KEY_NSPrincipalClass: NSApplication
INFOPLIST_KEY_NSMainStoryboardFile: Main
scheme: {}
dependencies: []

Expand Down

0 comments on commit f30ff95

Please sign in to comment.