diff --git a/src/popup/App.svelte b/src/popup/App.svelte
index 52cb2cb8..92fbc72a 100644
--- a/src/popup/App.svelte
+++ b/src/popup/App.svelte
@@ -545,13 +545,14 @@
Popup inactive on extension page
{:else if firstGuide}
- Welcome, first please:
+
Welcome, first use please:
+
to complete the initialization
{:else if initError}
@@ -716,13 +717,11 @@
}
.none {
- align-items: center;
- color: var(--text-color-disabled);
- display: flex;
font-weight: 600;
- justify-content: center;
- inset: 0;
- position: absolute;
+ color: var(--text-color-disabled);
+ text-align: center;
+ padding: 3rem 0;
+ line-height: 2.5;
}
.items.disabled {
diff --git a/xcode/Mac-App/AppDelegate.swift b/xcode/Mac-App/AppDelegate.swift
index 23e8a127..a834109c 100644
--- a/xcode/Mac-App/AppDelegate.swift
+++ b/xcode/Mac-App/AppDelegate.swift
@@ -7,6 +7,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
private var windowForego = false
private var windowLoaded = false
+ @IBOutlet weak var enbaleNativeLogger: NSMenuItem!
+
func application(_ application: NSApplication, open urls: [URL]) {
// if open panel is already open, stop processing the URL scheme
if NSApplication.shared.keyWindow?.accessibilityIdentifier() == "open-panel" { return }
@@ -26,6 +28,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
+ // Initialize menu items
+ enbaleNativeLogger.state = Preferences.enableLogger ? .on : .off
if windowForego { return }
let storyboard = NSStoryboard(name: "View", bundle: Bundle.main)
let windowController = storyboard.instantiateInitialController() as! NSWindowController
@@ -47,4 +51,20 @@ class AppDelegate: NSObject, NSApplicationDelegate {
return true
}
+ @IBAction func enableLogger(_ sender: NSMenuItem) {
+ if sender.state == .on {
+ Preferences.enableLogger = false
+ sender.state = .off
+ } else {
+ Preferences.enableLogger = true
+ sender.state = .on
+ }
+ }
+
+ @IBAction func applicationHelp(_ sender: NSMenuItem) {
+ if let url = URL(string: "https://github.com/quoid/userscripts") {
+ NSWorkspace.shared.open(url)
+ }
+ }
+
}
diff --git a/xcode/Mac-App/Base.lproj/Main.storyboard b/xcode/Mac-App/Base.lproj/Main.storyboard
index ed7ecf33..859f7dde 100644
--- a/xcode/Mac-App/Base.lproj/Main.storyboard
+++ b/xcode/Mac-App/Base.lproj/Main.storyboard
@@ -1,8 +1,8 @@
-
+
-
+
@@ -30,13 +30,42 @@
+
+
-
+
+
+
+
+
diff --git a/xcode/Mac-App/Functions.swift b/xcode/Mac-App/Functions.swift
index 61d753b7..94831a17 100644
--- a/xcode/Mac-App/Functions.swift
+++ b/xcode/Mac-App/Functions.swift
@@ -2,37 +2,10 @@ import AppKit
import SafariServices
import os
-let extensionIdentifier = Bundle.main.infoDictionary?["US_EXT_IDENTIFIER"] as! String
-fileprivate let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: #fileID)
+private let logger = USLogger(#fileID)
func getSaveLocationURL() -> URL {
- var url: URL
- // default url
- if #available(macOS 13.0, *) {
- url = getDocumentsDirectory().appending(path: "scripts")
- } else {
- url = getDocumentsDirectory().appendingPathComponent("scripts")
- }
- // if not in safari extension environment, replace with extension path
- if let bundleIdentifier = Bundle.main.bundleIdentifier, bundleIdentifier != extensionIdentifier {
- let s = url.absoluteString.replacingOccurrences(of: bundleIdentifier, with: extensionIdentifier)
- // avoid being encode again, decode first
- if let decodePath = s.removingPercentEncoding {
- url = URL(fileURLWithPath: decodePath, isDirectory: true)
- }
- }
- // bookmark url
- if let data = UserDefaults(suiteName: SharedDefaults.suiteName)?.data(forKey: SharedDefaults.keyName) {
- if let bookmarkURL = readBookmark(data: data, isSecure: false) {
- if directoryExists(path: bookmarkURL.path) {
- url = bookmarkURL
- } else {
- UserDefaults(suiteName: SharedDefaults.suiteName)?.removeObject(forKey: SharedDefaults.keyName)
- NSLog("removed shared bookmark because it's directory is non-existent, permanently deleted or in trash")
- }
- }
- }
- return url
+ return Preferences.scriptsDirectoryUrl
}
func setSaveLocationURL(url: URL) -> Bool {
@@ -42,17 +15,14 @@ func setSaveLocationURL(url: URL) -> Bool {
alert.runModal()
return false
}
- guard saveBookmark(url: url, isShared: true, keyName: SharedDefaults.keyName, isSecure: false) else {
- logger.error("\(#function, privacy: .public) - couldn't save new location from host app")
- return false
- }
- return true
+ Preferences.scriptsDirectoryUrl = url
+ return Preferences.scriptsDirectoryUrl == url
}
func sendExtensionMessage(name: String, userInfo: [String : Any]? = nil, completion: ((Error?) -> Void)? = nil) {
SFSafariApplication.dispatchMessage(
withName: name,
- toExtensionWithIdentifier: extensionIdentifier,
+ toExtensionWithIdentifier: extIdentifier,
userInfo: userInfo
) { error in // always be called
if error != nil {
@@ -83,6 +53,8 @@ func schemeChangeSaveLocation() {
let response = panel.runModal()
// check if clicked open button and there is a valid result
guard response == .OK, let url: URL = panel.urls.first else { return }
+ // revoke implicitly starts security-scoped access
+ defer { url.stopAccessingSecurityScopedResource() }
// check if path has indeed changed
if url.absoluteString == saveLocationURL.absoluteString { return }
// try set new save location path to bookmark
diff --git a/xcode/Mac-App/ViewController.swift b/xcode/Mac-App/ViewController.swift
index 3e2ffeaf..8b9a779a 100644
--- a/xcode/Mac-App/ViewController.swift
+++ b/xcode/Mac-App/ViewController.swift
@@ -2,7 +2,7 @@ import Cocoa
import SafariServices.SFSafariApplication
import os
-fileprivate let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: #fileID)
+private let logger = USLogger(#fileID)
class ViewController: NSViewController {
@@ -14,7 +14,6 @@ class ViewController: NSViewController {
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "??"
let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "??"
- let extensionID = extensionIdentifier
override func viewDidLoad() {
super.viewDidLoad()
@@ -35,13 +34,13 @@ class ViewController: NSViewController {
}
@objc func setExtensionState() {
- SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionID) { (state, error) in
+ SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extIdentifier) { (state, error) in
guard let state = state else {
self.enabledText.stringValue = "Safari Extension State Unknown"
if let error = error {
- logger.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)")
} else {
- logger.error("\(#function, privacy: .public) - couldn't get safari extension state in containing app")
+ logger?.error("\(#function, privacy: .public) - couldn't get safari extension state in containing app")
}
return
}
@@ -59,6 +58,8 @@ class ViewController: NSViewController {
panel.beginSheetModal(for: window, completionHandler: { response in
// check if clicked open button and there is a valid result
guard response == .OK, let url: URL = panel.urls.first else { return }
+ // revoke implicitly starts security-scoped access
+ defer { url.stopAccessingSecurityScopedResource() }
// check if path has indeed changed
if url.absoluteString == saveLocationURL.absoluteString { return }
// try set new save location path to bookmark
@@ -78,6 +79,6 @@ class ViewController: NSViewController {
}
@IBAction func openSafariExtensionPreferences(_ sender: AnyObject?) {
- SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionID)
+ SFSafariApplication.showPreferencesForExtension(withIdentifier: extIdentifier)
}
}
diff --git a/xcode/Safari-Extension/Functions.swift b/xcode/Safari-Extension/Functions.swift
index bd15f4ca..6f0c9053 100644
--- a/xcode/Safari-Extension/Functions.swift
+++ b/xcode/Safari-Extension/Functions.swift
@@ -2,7 +2,7 @@ import Foundation
import SafariServices
import os
-fileprivate let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: #fileID)
+private let logger = USLogger(#fileID)
// helpers
func getRequireLocation() -> URL {
@@ -53,76 +53,12 @@ func normalizeWeight(_ weight: String) -> String {
}
func getSaveLocation() -> URL? {
- #if os(iOS)
- if
- let sharedBookmarkData = UserDefaults(suiteName: SharedDefaults.suiteName)?.data(forKey: SharedDefaults.keyName),
- let bookmarkUrl = readBookmark(data: sharedBookmarkData, isSecure: true)
- {
- return bookmarkUrl
- } else {
- return nil
- }
- #elseif os(macOS)
- let standardDefaults = UserDefaults.standard
- let userSaveLocationKey = "userSaveLocation"
- var defaultSaveLocation:URL
-
- // get the default save location, if key doesn't exist write it to user defaults
- if let saveLocationValue = standardDefaults.url(forKey: "saveLocation") {
- defaultSaveLocation = saveLocationValue
- } else {
- logger.info("\(#function, privacy: .public) - default save location not set, writing to user defaults")
- let url = getDocumentsDirectory().appendingPathComponent("scripts")
- UserDefaults.standard.set(url, forKey: "saveLocation")
- defaultSaveLocation = url
- }
-
- // check if shared bookmark data exists
- // check if can get shared bookmark url
- // won't be able to if directory trashed
- guard
- let sharedBookmarkData = UserDefaults(suiteName: SharedDefaults.suiteName)?.data(forKey: SharedDefaults.keyName),
- let sharedBookmark = readBookmark(data: sharedBookmarkData, isSecure: false),
- directoryExists(path: sharedBookmark.path)
- else {
- // can't get shared bookmark, use default location and remove shared bookmark key from shared user defaults
- UserDefaults(suiteName: SharedDefaults.suiteName)?.removeObject(forKey: SharedDefaults.keyName)
- logger.info("\(#function, privacy: .public) - removed sharedbookmark because it was either permanently deleted or in trash")
- return defaultSaveLocation
- }
-
- // at this point, it's known sharedbookmark exists
- // check local bookmark exists, can read url from bookmark and if bookmark url == shared bookmark url
- // if local bookmark exists, no need to check if directory exists for it
- // can't think of an instance where shared bookmark directory exists (checked above), yet local bookmark directory does not
- if
- let userSaveLocationData = standardDefaults.data(forKey: userSaveLocationKey),
- let userSaveLocation = readBookmark(data: userSaveLocationData, isSecure: true),
- sharedBookmark == userSaveLocation
- {
- return userSaveLocation
- }
-
- // at this point one of the following conditions met
- // - local bookmark data doesn't exist
- // - for some reason can't get url from local bookmark data
- // - local bookmark url != shared bookmark url (user updated save location)
- // when any of those conditions are met, create new local bookmark from shared bookmark
- if saveBookmark(url: sharedBookmark, isShared: false, keyName: userSaveLocationKey, isSecure: true) {
- // read the newly saved bookmark and return it
- guard
- let localBookmarkData = standardDefaults.data(forKey: userSaveLocationKey),
- let localBookmarkUrl = readBookmark(data: localBookmarkData, isSecure: true)
- else {
- logger.error("\(#function, privacy: .public) - failed reading local bookmark")
- return nil
- }
- return localBookmarkUrl
- } else {
- logger.error("\(#function, privacy: .public) - could not save local version of shared bookmark")
- return nil
- }
- #endif
+#if os(iOS)
+ if Preferences.scriptsDirectoryUrl == getDefaultScriptsDirectoryUrl() { return nil }
+#endif
+ let url = Preferences.scriptsDirectoryUrl
+ logger?.debug("\(#function, privacy: .public) - \(url, privacy: .public)")
+ return url
}
func openSaveLocation() -> Bool {
@@ -146,7 +82,7 @@ func validateUrl(_ urlString: String) -> Bool {
if let decodedUrl = urlChecked.removingPercentEncoding {
urlChecked = decodedUrl
} else {
- logger.error("\(#function, privacy: .public) - failed at (1), couldn't decode url, \(urlString, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed at (1), couldn't decode url, \(urlString, privacy: .public)")
return false
}
}
@@ -154,7 +90,7 @@ func validateUrl(_ urlString: String) -> Bool {
if let encodedUrl = urlChecked.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
urlChecked = encodedUrl
} else {
- logger.error("\(#function, privacy: .public) - failed at (2), couldn't percent encode url, \(urlString, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed at (2), couldn't percent encode url, \(urlString, privacy: .public)")
return false
}
guard
@@ -162,7 +98,7 @@ func validateUrl(_ urlString: String) -> Bool {
let ptcl = parts["protocol"],
let path = parts["pathname"]
else {
- logger.error("\(#function, privacy: .public) - failed at (3) for \(urlString, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed at (3) for \(urlString, privacy: .public)")
return false
}
if
@@ -312,7 +248,7 @@ func updateManifest(with data: Manifest) -> Bool {
try fileContent.write(to: url, atomically: false, encoding: .utf8)
return true
} catch {
- logger.error("\(#function, privacy: .public) - failed to update manifest: \(error.localizedDescription, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed to update manifest: \(error.localizedDescription, privacy: .public)")
return false
}
}
@@ -345,7 +281,7 @@ func getManifest() -> Manifest {
}
func updateManifestMatches(_ optionalFilesArray: [[String: Any]] = []) -> Bool {
- logger.info("\(#function, privacy: .public) - started")
+ logger?.info("\(#function, privacy: .public) - started")
// only get all files if files were not provided
var files = [[String: Any]]()
if optionalFilesArray.isEmpty {
@@ -386,7 +322,7 @@ func updateManifestMatches(_ optionalFilesArray: [[String: Any]] = []) -> Bool {
if let index = manifest.declarativeNetRequest.firstIndex(of: filename) {
manifest.declarativeNetRequest.remove(at: index)
} else {
- logger.error("\(#function, privacy: .public) - failed to remove \(filename, privacy: .public) from dNR array")
+ logger?.error("\(#function, privacy: .public) - failed to remove \(filename, privacy: .public) from dNR array")
}
}
@@ -397,11 +333,11 @@ func updateManifestMatches(_ optionalFilesArray: [[String: Any]] = []) -> Bool {
manifest.include = updatePatternDict(filename, included, manifest.include)
if !updateManifest(with: manifest) {
- logger.error("\(#function, privacy: .public) - failed to update manifest matches")
+ logger?.error("\(#function, privacy: .public) - failed to update manifest matches")
return false
}
}
- logger.info("\(#function, privacy: .public) - complete")
+ logger?.info("\(#function, privacy: .public) - completed")
return true
}
@@ -416,7 +352,7 @@ func updatePatternDict(_ filename: String, _ filePatterns: [String], _ manifestK
for key in keys {
// key is an array of filenames
guard let filenames = returnDictionary[key] else {
- logger.error("\(#function, privacy: .public) - failed to get values for manifest key, \(key, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed to get values for manifest key, \(key, privacy: .public)")
continue
}
for name in filenames {
@@ -463,19 +399,19 @@ func updatePatternDict(_ filename: String, _ filePatterns: [String], _ manifestK
}
func updateManifestRequired(_ optionalFilesArray: [[String: Any]] = []) -> Bool {
- logger.info("\(#function, privacy: .public) - started")
+ logger?.info("\(#function, privacy: .public) - started")
// only get all files if files were not provided
var files = [[String: Any]]()
if optionalFilesArray.isEmpty {
guard let getFiles = getAllFiles() else {
- logger.info("\(#function, privacy: .public) - count not get files")
+ logger?.info("\(#function, privacy: .public) - count not get files")
return false
}
files = getFiles
} else {
files = optionalFilesArray
}
- logger.info("\(#function, privacy: .public) - will loop through \(files.count, privacy: .public)")
+ logger?.info("\(#function, privacy: .public) - will loop through \(files.count, privacy: .public)")
var manifest = getManifest()
for file in files {
// can be force unwrapped because getAllFiles didn't return nil
@@ -483,10 +419,10 @@ func updateManifestRequired(_ optionalFilesArray: [[String: Any]] = []) -> Bool
let metadata = file["metadata"] as! [String: [String]]
let type = file["type"] as! String
let required = metadata["require"] ?? []
- logger.info("\(#function, privacy: .public) - begin \(filename, privacy: .public)")
+ logger?.info("\(#function, privacy: .public) - begin \(filename, privacy: .public)")
// get required resources for file, if fail, skip updating manifest
if !getRequiredCode(filename, required, type) {
- logger.error("\(#function, privacy: .public) - couldn't fetch remote content for \(filename, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - couldn't fetch remote content for \(filename, privacy: .public)")
continue
}
@@ -504,21 +440,21 @@ func updateManifestRequired(_ optionalFilesArray: [[String: Any]] = []) -> Bool
if !r.isEmpty && r != manifest.require[filename] {
manifest.require[filename] = r
if !updateManifest(with: manifest) {
- logger.error("\(#function, privacy: .public) - couldn't update manifest when getting required resources")
+ logger?.error("\(#function, privacy: .public) - couldn't update manifest when getting required resources")
}
}
- logger.info("\(#function, privacy: .public) - end \(filename, privacy: .public)")
+ logger?.info("\(#function, privacy: .public) - end \(filename, privacy: .public)")
}
- logger.info("\(#function, privacy: .public) - complete")
+ logger?.info("\(#function, privacy: .public) - completed")
return true
}
func updateManifestDeclarativeNetRequests(_ optionalFilesArray: [[String: Any]] = []) -> Bool {
- logger.info("\(#function, privacy: .public) - started")
+ logger?.info("\(#function, privacy: .public) - started")
var files = [[String: Any]]()
if optionalFilesArray.isEmpty {
guard let getFiles = getAllFiles() else {
- logger.error("\(#function, privacy: .public) - failed at (1)")
+ logger?.error("\(#function, privacy: .public) - failed at (1)")
return false
}
files = getFiles
@@ -576,16 +512,16 @@ func updateManifestDeclarativeNetRequests(_ optionalFilesArray: [[String: Any]]
}
}
if update, !updateManifest(with: manifest) {
- logger.error("\(#function, privacy: .public) - failed at (2)")
+ logger?.error("\(#function, privacy: .public) - failed at (2)")
return false
}
}
- logger.info("\(#function, privacy: .public) - complete")
+ logger?.info("\(#function, privacy: .public) - completed")
return true
}
func purgeManifest(_ optionalFilesArray: [[String: Any]] = []) -> Bool {
- logger.info("\(#function, privacy: .public) - started")
+ logger?.info("\(#function, privacy: .public) - started")
// purge all manifest keys of any stale entries
var update = false, manifest = getManifest(), allSaveLocationFilenames = [String]()
// only get all files if files were not provided
@@ -611,14 +547,14 @@ func purgeManifest(_ optionalFilesArray: [[String: Any]] = []) -> Bool {
if let index = manifest.match[pattern]?.firstIndex(of: filename) {
manifest.match[pattern]?.remove(at: index)
update = true
- logger.info("\(#function, privacy: .public) - Could not find \(filename, privacy: .public) in save location, removed from match pattern - \(pattern, privacy: .public)")
+ logger?.info("\(#function, privacy: .public) - Could not find \(filename, privacy: .public) in save location, removed from match pattern - \(pattern, privacy: .public)")
}
}
}
if let length = manifest.match[pattern]?.count {
if length < 1, let ind = manifest.match.index(forKey: pattern) {
manifest.match.remove(at: ind)
- logger.info("\(#function, privacy: .public) - No more files for \(pattern, privacy: .public) match pattern, removed from manifest")
+ logger?.info("\(#function, privacy: .public) - No more files for \(pattern, privacy: .public) match pattern, removed from manifest")
}
}
}
@@ -628,14 +564,14 @@ func purgeManifest(_ optionalFilesArray: [[String: Any]] = []) -> Bool {
if let index = manifest.excludeMatch[pattern]?.firstIndex(of: filename) {
manifest.excludeMatch[pattern]?.remove(at: index)
update = true
- logger.info("\(#function, privacy: .public) - Could not find \(filename, privacy: .public) in save location, removed from exclude-match pattern - \(pattern, privacy: .public)")
+ logger?.info("\(#function, privacy: .public) - Could not find \(filename, privacy: .public) in save location, removed from exclude-match pattern - \(pattern, privacy: .public)")
}
}
}
if let length = manifest.excludeMatch[pattern]?.count {
if length < 1, let ind = manifest.excludeMatch.index(forKey: pattern) {
manifest.excludeMatch.remove(at: ind)
- logger.info("\(#function, privacy: .public) - No more files for \(pattern, privacy: .public) exclude-match pattern, removed from manifest")
+ logger?.info("\(#function, privacy: .public) - No more files for \(pattern, privacy: .public) exclude-match pattern, removed from manifest")
}
}
}
@@ -645,14 +581,14 @@ func purgeManifest(_ optionalFilesArray: [[String: Any]] = []) -> Bool {
if let index = manifest.exclude[pattern]?.firstIndex(of: filename) {
manifest.exclude[pattern]?.remove(at: index)
update = true
- logger.info("\(#function, privacy: .public) - Could not find \(filename, privacy: .public) in save location, removed from exclude pattern - \(pattern, privacy: .public)")
+ logger?.info("\(#function, privacy: .public) - Could not find \(filename, privacy: .public) in save location, removed from exclude pattern - \(pattern, privacy: .public)")
}
}
}
if let length = manifest.exclude[pattern]?.count {
if length < 1, let ind = manifest.exclude.index(forKey: pattern) {
manifest.exclude.remove(at: ind)
- logger.info("\(#function, privacy: .public) - No more files for \(pattern, privacy: .public) exclude pattern, removed from manifest")
+ logger?.info("\(#function, privacy: .public) - No more files for \(pattern, privacy: .public) exclude pattern, removed from manifest")
}
}
}
@@ -662,14 +598,14 @@ func purgeManifest(_ optionalFilesArray: [[String: Any]] = []) -> Bool {
if let index = manifest.include[pattern]?.firstIndex(of: filename) {
manifest.include[pattern]?.remove(at: index)
update = true
- logger.info("\(#function, privacy: .public) - Could not find \(filename, privacy: .public) in save location, removed from exclude pattern - \(pattern, privacy: .public)")
+ logger?.info("\(#function, privacy: .public) - Could not find \(filename, privacy: .public) in save location, removed from exclude pattern - \(pattern, privacy: .public)")
}
}
}
if let length = manifest.include[pattern]?.count {
if length < 1, let ind = manifest.include.index(forKey: pattern) {
manifest.include.remove(at: ind)
- logger.info("\(#function, privacy: .public) - No more files for \(pattern, privacy: .public) exclude pattern, removed from manifest")
+ logger?.info("\(#function, privacy: .public) - No more files for \(pattern, privacy: .public) exclude pattern, removed from manifest")
}
}
}
@@ -680,10 +616,10 @@ func purgeManifest(_ optionalFilesArray: [[String: Any]] = []) -> Bool {
manifest.require.remove(at: index)
// remove associated resources
if !getRequiredCode(filename, [], (filename as NSString).pathExtension) {
- logger.error("\(#function, privacy: .public) - failed to remove required resources when purging \(filename, privacy: .public) from manifest required records")
+ logger?.error("\(#function, privacy: .public) - failed to remove required resources when purging \(filename, privacy: .public) from manifest required records")
}
update = true
- logger.info("\(#function, privacy: .public) - No more required resources for \(filename, privacy: .public), removed from manifest along with resource folder")
+ logger?.info("\(#function, privacy: .public) - No more required resources for \(filename, privacy: .public), removed from manifest along with resource folder")
}
}
}
@@ -693,7 +629,7 @@ func purgeManifest(_ optionalFilesArray: [[String: Any]] = []) -> Bool {
if let index = manifest.disabled.firstIndex(of: filename) {
manifest.disabled.remove(at: index)
update = true
- logger.info("\(#function, privacy: .public) - Could not find \(filename, privacy: .public) in save location, removed from disabled")
+ logger?.info("\(#function, privacy: .public) - Could not find \(filename, privacy: .public) in save location, removed from disabled")
}
}
}
@@ -703,7 +639,7 @@ func purgeManifest(_ optionalFilesArray: [[String: Any]] = []) -> Bool {
if let index = manifest.declarativeNetRequest.firstIndex(of: filename) {
manifest.declarativeNetRequest.remove(at: index)
update = true
- logger.info("\(#function, privacy: .public) - Could not find \(filename, privacy: .public) in save location, removed from dNR")
+ logger?.info("\(#function, privacy: .public) - Could not find \(filename, privacy: .public) in save location, removed from dNR")
}
}
}
@@ -712,14 +648,14 @@ func purgeManifest(_ optionalFilesArray: [[String: Any]] = []) -> Bool {
if !defaultSettings.keys.contains(setting.key) {
manifest.settings.removeValue(forKey: setting.key)
update = true
- logger.info("\(#function, privacy: .public) - Removed obsolete setting - \(setting.key, privacy: .public)")
+ logger?.info("\(#function, privacy: .public) - Removed obsolete setting - \(setting.key, privacy: .public)")
}
}
if update, !updateManifest(with: manifest) {
- logger.error("\(#function, privacy: .public) - failed to purge manifest")
+ logger?.error("\(#function, privacy: .public) - failed to purge manifest")
return false
}
- logger.info("\(#function, privacy: .public) - complete")
+ logger?.info("\(#function, privacy: .public) - completed")
return true
}
@@ -737,7 +673,7 @@ func checkSettings() -> Bool {
}
}
if update, !updateManifest(with: manifest) {
- logger.error("\(#function, privacy: .public) - failed to update manifest settings")
+ logger?.error("\(#function, privacy: .public) - failed to update manifest settings")
return false
}
return true
@@ -747,7 +683,7 @@ func updateSettings(_ settings: [String: String]) -> Bool {
var manifest = getManifest()
manifest.settings = settings
if updateManifest(with: manifest) != true {
- logger.error("\(#function, privacy: .public) - failed to update settings")
+ logger?.error("\(#function, privacy: .public) - failed to update settings")
return false
}
return true
@@ -755,12 +691,13 @@ func updateSettings(_ settings: [String: String]) -> Bool {
// files
func getAllFiles(includeCode: Bool = false) -> [[String: Any]]? {
+ logger?.info("\(#function, privacy: .public) - started")
// returns all files of proper type with filenames, metadata & more
var files = [[String: Any]]()
let fm = FileManager.default
let manifest = getManifest()
guard let saveLocation = getSaveLocation() else {
- logger.error("\(#function, privacy: .public) - failed at (1)")
+ logger?.error("\(#function, privacy: .public) - failed at (1)")
return nil
}
// security scope
@@ -770,7 +707,7 @@ func getAllFiles(includeCode: Bool = false) -> [[String: Any]]? {
}
// get all file urls within save location
guard let urls = try? fm.contentsOfDirectory(at: saveLocation, includingPropertiesForKeys: []) else {
- logger.error("\(#function, privacy: .public) - failed at (2)")
+ logger?.error("\(#function, privacy: .public) - failed at (2)")
return nil
}
for url in urls {
@@ -788,7 +725,7 @@ func getAllFiles(includeCode: Bool = false) -> [[String: Any]]? {
let metadata = parsed["metadata"] as? [String: [String]],
let type = filename.split(separator: ".").last
else {
- logger.info("\(#function, privacy: .public) - ignoring \(filename, privacy: .public), file missing or metadata missing from file contents")
+ logger?.info("\(#function, privacy: .public) - ignoring \(filename, privacy: .public), file missing or metadata missing from file contents")
continue
}
fileData["canUpdate"] = false
@@ -819,7 +756,7 @@ func getAllFiles(includeCode: Bool = false) -> [[String: Any]]? {
}
files.append(fileData)
}
- logger.info("\(#function, privacy: .public) - completed")
+ logger?.info("\(#function, privacy: .public) - completed")
return files
}
@@ -833,7 +770,7 @@ func getRequiredCode(_ filename: String, _ resources: [String], _ fileType: Stri
do {
try FileManager.default.removeItem(at: directory)
} catch {
- logger.error("\(#function, privacy: .public) - failed to remove directory: \(error.localizedDescription, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed to remove directory: \(error.localizedDescription, privacy: .public)")
}
}
return true
@@ -845,7 +782,7 @@ func getRequiredCode(_ filename: String, _ resources: [String], _ fileType: Stri
// get the path of the url string
guard let resourceUrlPath = URLComponents(string: resourceUrlString)?.path else {
// if path can not be obtained, skip and log
- logger.info("\(#function, privacy: .public) - failed to get path on \(filename, privacy: .public) for \(resourceUrlString, privacy: .public)")
+ logger?.info("\(#function, privacy: .public) - failed to get path on \(filename, privacy: .public) for \(resourceUrlString, privacy: .public)")
continue
}
// skip urls pointing to files of different types
@@ -861,13 +798,13 @@ func getRequiredCode(_ filename: String, _ resources: [String], _ fileType: Stri
// check if file specific folder exists at requires directory
if !FileManager.default.fileExists(atPath: directory.path) {
guard ((try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: false)) != nil) else {
- logger.info("\(#function, privacy: .public) - failed to create required code directory for \(filename, privacy: .public)")
+ logger?.info("\(#function, privacy: .public) - failed to create required code directory for \(filename, privacy: .public)")
return false
}
}
// finally write file to directory
guard ((try? contents.write(to: fileURL, atomically: false, encoding: .utf8)) != nil) else {
- logger.info("\(#function, privacy: .public) - failed to write content to file for \(filename, privacy: .public) from \(resourceUrlString, privacy: .public)")
+ logger?.info("\(#function, privacy: .public) - failed to write content to file for \(filename, privacy: .public) from \(resourceUrlString, privacy: .public)")
return false
}
}
@@ -885,13 +822,13 @@ func getRequiredCode(_ filename: String, _ resources: [String], _ fileType: Stri
for abandonFileUrl in abandonedUrls {
do {
try FileManager.default.removeItem(at: abandonFileUrl)
- logger.info("\(#function, privacy: .public) - cleanup abandoned resource: \(unsanitize(abandonFileUrl.lastPathComponent), privacy: .public)")
+ logger?.info("\(#function, privacy: .public) - cleanup abandoned resource: \(unsanitize(abandonFileUrl.lastPathComponent), privacy: .public)")
} catch {
- logger.error("\(#function, privacy: .public) - failed to remove abandoned resource: \(error.localizedDescription, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed to remove abandoned resource: \(error.localizedDescription, privacy: .public)")
}
}
} catch {
- logger.error("\(#function, privacy: .public) - failed to cleanup resources: \(error.localizedDescription, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed to cleanup resources: \(error.localizedDescription, privacy: .public)")
}
return true
}
@@ -901,7 +838,7 @@ func checkForRemoteUpdates(_ optionalFilesArray: [[String: Any]] = []) -> [[Stri
var files = [[String: Any]]()
if optionalFilesArray.isEmpty {
guard let getFiles = getAllFiles() else {
- logger.error("\(#function, privacy: .public) - failed at (1)")
+ logger?.error("\(#function, privacy: .public) - failed at (1)")
return nil
}
files = getFiles
@@ -917,7 +854,7 @@ func checkForRemoteUpdates(_ optionalFilesArray: [[String: Any]] = []) -> [[Stri
let metadata = file["metadata"] as! [String: [String]]
let type = file["type"] as! String
let name = metadata["name"]![0]
- logger.info("\(#function, privacy: .public) - Checking for remote updates for \(filename, privacy: .public)")
+ logger?.info("\(#function, privacy: .public) - Checking for remote updates for \(filename, privacy: .public)")
if canUpdate {
let currentVersion = metadata["version"]![0]
let updateUrl = metadata["updateURL"]![0]
@@ -929,7 +866,7 @@ func checkForRemoteUpdates(_ optionalFilesArray: [[String: Any]] = []) -> [[Stri
let remoteMetadata = remoteFileContentsParsed["metadata"] as? [String: [String]],
let remoteVersion = remoteMetadata["version"]?[0]
else {
- logger.error("\(#function, privacy: .public) - failed to parse remote file contents")
+ logger?.error("\(#function, privacy: .public) - failed to parse remote file contents")
return nil
}
let remoteVersionNewer = isVersionNewer(currentVersion, remoteVersion)
@@ -938,24 +875,24 @@ func checkForRemoteUpdates(_ optionalFilesArray: [[String: Any]] = []) -> [[Stri
}
}
}
- logger.info("\(#function, privacy: .public) - Finished checking for remote updates for \(files.count, privacy: .public) files")
+ logger?.info("\(#function, privacy: .public) - Finished checking for remote updates for \(files.count, privacy: .public) files")
return hasUpdates
}
func getRemoteFileContents(_ url: String) -> String? {
- logger.info("\(#function, privacy: .public) - started for \(url, privacy: .public)")
+ logger?.info("\(#function, privacy: .public) - started for \(url, privacy: .public)")
// if url is http change to https
var urlChecked = url
if urlChecked.hasPrefix("http:") {
urlChecked = urlChecked.replacingOccurrences(of: "http:", with: "https:")
- logger.info("\(#function, privacy: .public) - \(url, privacy: .public) is using insecure http, attempt to fetch remote content with https")
+ logger?.info("\(#function, privacy: .public) - \(url, privacy: .public) is using insecure http, attempt to fetch remote content with https")
}
// if the url is already encoded, decode it
if isEncoded(urlChecked) {
if let decodedUrl = urlChecked.removingPercentEncoding {
urlChecked = decodedUrl
} else {
- logger.error("\(#function, privacy: .public) - failed at (1), couldn't decode url, \(url, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed at (1), couldn't decode url, \(url, privacy: .public)")
return nil
}
}
@@ -963,12 +900,12 @@ func getRemoteFileContents(_ url: String) -> String? {
if let encodedUrl = urlChecked.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
urlChecked = encodedUrl
} else {
- logger.error("\(#function, privacy: .public) - failed at (2), couldn't percent encode url, \(url, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed at (2), couldn't percent encode url, \(url, privacy: .public)")
return nil
}
// convert url string to url
guard let solidURL = URL(string: urlChecked) else {
- logger.error("\(#function, privacy: .public) - failed at (3), couldn't convert string to url, \(url, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed at (3), couldn't convert string to url, \(url, privacy: .public)")
return nil
}
var contents = ""
@@ -991,10 +928,10 @@ func getRemoteFileContents(_ url: String) -> String? {
// if made it to this point and contents still an empty string, something went wrong with the request
if contents.isEmpty {
- logger.error("\(#function, privacy: .public) - failed at (4), contents empty, \(url, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed at (4), contents empty, \(url, privacy: .public)")
return nil
}
- logger.info("\(#function, privacy: .public) - complete for \(url, privacy: .public)")
+ logger?.info("\(#function, privacy: .public) - completed for \(url, privacy: .public)")
return contents
}
@@ -1004,7 +941,7 @@ func updateAllFiles(_ optionalFilesArray: [[String: Any]] = []) -> Bool {
let filesWithUpdates = checkForRemoteUpdates(optionalFilesArray),
let saveLocation = getSaveLocation()
else {
- logger.error("\(#function, privacy: .public) - failed to update files (1)")
+ logger?.error("\(#function, privacy: .public) - failed to update files (1)")
return false
}
// security scope
@@ -1022,7 +959,7 @@ func updateAllFiles(_ optionalFilesArray: [[String: Any]] = []) -> Bool {
let metadata = parsed["metadata"] as? [String: [String]],
let updateUrl = metadata["updateURL"]?[0]
else {
- logger.error("\(#function, privacy: .public) - failed to update files (2)")
+ logger?.error("\(#function, privacy: .public) - failed to update files (2)")
continue
}
let downloadUrl = metadata["downloadURL"] != nil ? metadata["downloadURL"]![0] : updateUrl
@@ -1030,10 +967,10 @@ func updateAllFiles(_ optionalFilesArray: [[String: Any]] = []) -> Bool {
let remoteFileContents = getRemoteFileContents(downloadUrl),
((try? remoteFileContents.write(to: fileUrl, atomically: false, encoding: .utf8)) != nil)
else {
- logger.error("\(#function, privacy: .public) - failed to update files (3)")
+ logger?.error("\(#function, privacy: .public) - failed to update files (3)")
continue
}
- logger.info("\(#function, privacy: .public) - updated \(filename, privacy: .public) with contents fetched from \(downloadUrl, privacy: .public)")
+ logger?.info("\(#function, privacy: .public) - updated \(filename, privacy: .public) with contents fetched from \(downloadUrl, privacy: .public)")
}
return true
}
@@ -1041,7 +978,7 @@ func updateAllFiles(_ optionalFilesArray: [[String: Any]] = []) -> Bool {
func toggleFile(_ filename: String,_ action: String) -> Bool {
// if file doesn't exist return false
guard let saveLocation = getSaveLocation() else {
- logger.error("\(#function, privacy: .public) - failed at (1)")
+ logger?.error("\(#function, privacy: .public) - failed at (1)")
return false
}
let didStartAccessing = saveLocation.startAccessingSecurityScopedResource()
@@ -1050,7 +987,7 @@ func toggleFile(_ filename: String,_ action: String) -> Bool {
}
let path = saveLocation.appendingPathComponent(filename).path
if !FileManager.default.fileExists(atPath: path) {
- logger.error("\(#function, privacy: .public) - failed at (2)")
+ logger?.error("\(#function, privacy: .public) - failed at (2)")
return false
}
var manifest = getManifest()
@@ -1063,13 +1000,13 @@ func toggleFile(_ filename: String,_ action: String) -> Bool {
// remove filename from disabled array if enabling
if (action == "enable") {
guard let index = manifest.disabled.firstIndex(of: filename) else {
- logger.error("\(#function, privacy: .public) - failed at (3)")
+ logger?.error("\(#function, privacy: .public) - failed at (3)")
return false
}
manifest.disabled.remove(at: index)
}
if !updateManifest(with: manifest) {
- logger.error("\(#function, privacy: .public) - failed at (4)")
+ logger?.error("\(#function, privacy: .public) - failed at (4)")
return false
}
return true
@@ -1085,7 +1022,7 @@ func checkDefaultDirectories() -> Bool {
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: false)
} catch {
// could not create the save location directory, show error
- logger.error("\(#function, privacy: .public) - failed at (1) - \(url, privacy: .public) - \(error.localizedDescription, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed at (1) - \(url, privacy: .public) - \(error.localizedDescription, privacy: .public)")
return false
}
}
@@ -1100,7 +1037,7 @@ func getUrlProps(_ url: String) -> [String: String]? {
let ptcl = parts.scheme,
let host = parts.host
else {
- logger.error("\(#function, privacy: .public) - failed to parse url")
+ logger?.error("\(#function, privacy: .public) - failed to parse url")
return nil
}
var search = ""
@@ -1133,7 +1070,7 @@ func match(_ url: String, _ matchPattern: String) -> Bool {
let host = parts["host"],
var path = parts["pathname"]
else {
- logger.error("\(#function, privacy: .public) - invalid url \(url, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - invalid url \(url, privacy: .public)")
return false
}
@@ -1155,7 +1092,7 @@ func match(_ url: String, _ matchPattern: String) -> Bool {
let partsPatternReg = try! NSRegularExpression(pattern: partsPattern, options: .caseInsensitive)
let range = NSMakeRange(0, matchPattern.utf16.count)
guard let parts = partsPatternReg.firstMatch(in: matchPattern, options: [], range: range) else {
- logger.error("\(#function, privacy: .public) - malformed regex match pattern")
+ logger?.error("\(#function, privacy: .public) - malformed regex match pattern")
return false
}
// ensure url protocol matches pattern protocol
@@ -1169,13 +1106,13 @@ func match(_ url: String, _ matchPattern: String) -> Bool {
hostPattern = hostPattern.replacingOccurrences(of: "^*$", with: ".*")
hostPattern = hostPattern.replacingOccurrences(of: "*\\.", with: "(.*\\.)?")
guard let hostRegEx = try? NSRegularExpression(pattern: hostPattern, options: .caseInsensitive) else {
- logger.error("\(#function, privacy: .public) - invalid host regex")
+ logger?.error("\(#function, privacy: .public) - invalid host regex")
return false
}
// construct path regex from matchPattern
let matchPatternPath = matchPattern[Range(parts.range(at: 3), in: matchPattern)!]
guard let pathRegEx = stringToRegex(String(matchPatternPath)) else {
- logger.error("\(#function, privacy: .public) - invalid path regex")
+ logger?.error("\(#function, privacy: .public) - invalid path regex")
return false
}
guard
@@ -1193,13 +1130,13 @@ func include(_ url: String,_ pattern: String) -> Bool {
if pattern.hasPrefix("/") && pattern.hasSuffix("/") {
let p = String(pattern.dropFirst().dropLast())
guard let exp = try? NSRegularExpression(pattern: p, options: .caseInsensitive) else {
- logger.error("\(#function, privacy: .public) - invalid regex")
+ logger?.error("\(#function, privacy: .public) - invalid regex")
return false
}
regex = exp
} else {
guard let exp = stringToRegex(pattern) else {
- logger.error("\(#function, privacy: .public) - coudn't convert string to regex")
+ logger?.error("\(#function, privacy: .public) - coudn't convert string to regex")
return false
}
regex = exp
@@ -1211,8 +1148,8 @@ func include(_ url: String,_ pattern: String) -> Bool {
}
func getMatchedFiles(_ url: String, _ optionalManifest: Manifest?, _ checkBlocklist: Bool) -> [String] {
- logger.info("\(#function, privacy: .public) - Getting matched files for \(url, privacy: .private(mask: .hash))")
- // logger.debug("\(#function, privacy: .public) - Getting matched files for \(url, privacy: .public)")
+ logger?.info("\(#function, privacy: .public) - Getting matched files for \(url, privacy: .private(mask: .hash))")
+ // logger?.debug("\(#function, privacy: .public) - Getting matched files for \(url, privacy: .public)")
let manifest = optionalManifest ?? getManifest()
// filenames that should not load for the passed url
@@ -1245,7 +1182,7 @@ func getMatchedFiles(_ url: String, _ optionalManifest: Manifest?, _ checkBlockl
for pattern in excludeMatchPatterns {
if match(url, pattern) {
guard let filenames = manifest.excludeMatch[pattern] else {
- logger.error("\(#function, privacy: .public) - failed at (2) for \(pattern, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed at (2) for \(pattern, privacy: .public)")
continue
}
excludedFilenames = excludedFilenames.union(filenames)
@@ -1254,7 +1191,7 @@ func getMatchedFiles(_ url: String, _ optionalManifest: Manifest?, _ checkBlockl
for exp in excludeExpressions {
if include(url, exp) {
guard let filenames = manifest.exclude[exp] else {
- logger.error("\(#function, privacy: .public) - failed at (3) for \(exp, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed at (3) for \(exp, privacy: .public)")
continue
}
excludedFilenames = excludedFilenames.union(filenames)
@@ -1263,7 +1200,7 @@ func getMatchedFiles(_ url: String, _ optionalManifest: Manifest?, _ checkBlockl
for pattern in matchPatterns {
if match(url, pattern) {
guard let filenames = manifest.match[pattern] else {
- logger.error("\(#function, privacy: .public) - failed at (4) for \(pattern, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed at (4) for \(pattern, privacy: .public)")
continue
}
matchedFilenames = matchedFilenames.union(filenames)
@@ -1272,15 +1209,15 @@ func getMatchedFiles(_ url: String, _ optionalManifest: Manifest?, _ checkBlockl
for exp in includeExpressions {
if include(url, exp) {
guard let filenames = manifest.include[exp] else {
- logger.error("\(#function, privacy: .public) - failed at (5) for \(exp, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed at (5) for \(exp, privacy: .public)")
continue
}
matchedFilenames = matchedFilenames.union(filenames)
}
}
matchedFilenames = matchedFilenames.subtracting(excludedFilenames)
- logger.info("\(#function, privacy: .public) - Got \(matchedFilenames.count) matched files for \(url, privacy: .private(mask: .hash))")
- // logger.debug("\(#function, privacy: .public) - Got \(matchedFilenames.count) matched files for \(url, privacy: .public)")
+ logger?.info("\(#function, privacy: .public) - Got \(matchedFilenames.count) matched files for \(url, privacy: .private(mask: .hash))")
+ // logger?.debug("\(#function, privacy: .public) - Got \(matchedFilenames.count) matched files for \(url, privacy: .public)")
return Array(matchedFilenames)
}
@@ -1291,7 +1228,7 @@ func getCode(_ filenames: [String], _ isTop: Bool)-> [String: Any]? {
var menuFiles = [Any]()
guard let saveLocation = getSaveLocation() else {
- logger.error("\(#function, privacy: .public) - failed at (1)")
+ logger?.error("\(#function, privacy: .public) - failed at (1)")
return nil
}
@@ -1302,7 +1239,7 @@ func getCode(_ filenames: [String], _ isTop: Bool)-> [String: Any]? {
let type = filename.split(separator: ".").last
else {
// if guard fails, log error continue to next file
- logger.error("\(#function, privacy: .public) - failed at (2) for \(filename, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed at (2) for \(filename, privacy: .public)")
continue
}
// can force unwrap b/c getFileContentsParsed ensures metadata exists
@@ -1424,7 +1361,7 @@ func getCode(_ filenames: [String], _ isTop: Bool)-> [String: Any]? {
if let requiredContent = try? String(contentsOf: requiredFileURL, encoding: .utf8) {
code = "\(requiredContent)\n\(code)"
} else {
- logger.error("\(#function, privacy: .public) - failed at (3) for \(require, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed at (3) for \(require, privacy: .public)")
}
}
}
@@ -1469,7 +1406,7 @@ func getCode(_ filenames: [String], _ isTop: Bool)-> [String: Any]? {
func getFileContentsParsed(_ url: URL) -> [String: Any]? {
guard let saveLocation = getSaveLocation() else {
- logger.error("\(#function, privacy: .public) - failed at (1)")
+ logger?.error("\(#function, privacy: .public) - failed at (1)")
return nil
}
// security scope
@@ -1493,7 +1430,7 @@ func getInjectionFilenames(_ url: String) -> [String]? {
let manifest = getManifest()
let matched = getMatchedFiles(url, manifest, true)
guard let active = manifest.settings["active"] else {
- logger.error("\(#function, privacy: .public) - failed at (1)")
+ logger?.error("\(#function, privacy: .public) - failed at (1)")
return nil
}
// if injection is disabled return empty array
@@ -1510,7 +1447,7 @@ func getRequestScripts() -> [[String: String]]? {
// check the manifest to see if injection is enabled
let manifest = getManifest()
guard let active = manifest.settings["active"] else {
- logger.error("\(#function, privacy: .public) - failed at (1)")
+ logger?.error("\(#function, privacy: .public) - failed at (1)")
return nil
}
// if not enabled, do not apply any net requests, ie. return empty array
@@ -1518,7 +1455,7 @@ func getRequestScripts() -> [[String: String]]? {
return requestScripts
}
guard let files = getAllFiles(includeCode: true) else {
- logger.error("\(#function, privacy: .public) - failed at (2)")
+ logger?.error("\(#function, privacy: .public) - failed at (2)")
return nil
}
for file in files {
@@ -1544,7 +1481,7 @@ func getContextMenuScripts() -> [String: Any]? {
// check the manifest to see if injection is enabled
let manifest = getManifest()
guard let active = manifest.settings["active"] else {
- logger.error("\(#function, privacy: .public) - failed at (1)")
+ logger?.error("\(#function, privacy: .public) - failed at (1)")
return nil
}
// if not enabled return empty array
@@ -1553,17 +1490,17 @@ func getContextMenuScripts() -> [String: Any]? {
}
// get all files at save location
guard let files = getAllFiles() else {
- logger.error("\(#function, privacy: .public) - failed at (2)")
+ logger?.error("\(#function, privacy: .public) - failed at (2)")
return nil
}
// loop through files and find @run-at context-menu script filenames
for file in files {
guard let filename = file["filename"] as? String else {
- logger.error("\(#function, privacy: .public) - failed at (3), couldn't get filename")
+ logger?.error("\(#function, privacy: .public) - failed at (3), couldn't get filename")
continue
}
guard let fileMetadata = file["metadata"] as? [String: [String]] else {
- logger.error("\(#function, privacy: .public) - failed at (4), couldn't get metadata for \(filename, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed at (4), couldn't get metadata for \(filename, privacy: .public)")
continue
}
let runAt = fileMetadata["run-at"]?[0] ?? "document-end"
@@ -1574,7 +1511,7 @@ func getContextMenuScripts() -> [String: Any]? {
}
// get and return script objects for all context-menu scripts
guard let scripts = getCode(menuFilenames, true) else {
- logger.error("\(#function, privacy: .public) - failed at (5)")
+ logger?.error("\(#function, privacy: .public) - failed at (5)")
return nil
}
return scripts
@@ -1593,7 +1530,7 @@ func getPopupMatches(_ url: String, _ subframeUrls: [String]) -> [[String: Any]]
guard
let files = getAllFiles()
else {
- logger.error("\(#function, privacy: .public) - failed at (1)")
+ logger?.error("\(#function, privacy: .public) - failed at (1)")
return nil
}
// filter out the files that are present in both files and matched
@@ -1656,7 +1593,7 @@ func getPopupBadgeCount(_ url: String, _ subframeUrls: [String]) -> Int? {
guard
var matches = getPopupMatches(url, subframeUrls)
else {
- logger.error("\(#function, privacy: .public) - failed at (1)")
+ logger?.error("\(#function, privacy: .public) - failed at (1)")
return nil
}
for pattern in manifest.blacklist {
@@ -1670,7 +1607,7 @@ func getPopupBadgeCount(_ url: String, _ subframeUrls: [String]) -> Int? {
func popupUpdateSingle(_ filename: String, _ url: String, _ subframeUrls: [String]) -> [[String: Any]]? {
guard let saveLocation = getSaveLocation() else {
- logger.error("\(#function, privacy: .public) - failed at (1)")
+ logger?.error("\(#function, privacy: .public) - failed at (1)")
return nil
}
// security scope
@@ -1685,7 +1622,7 @@ func popupUpdateSingle(_ filename: String, _ url: String, _ subframeUrls: [Strin
let metadata = parsed["metadata"] as? [String: [String]],
let updateUrl = metadata["updateURL"]?[0]
else {
- logger.error("\(#function, privacy: .public) - failed at (2)")
+ logger?.error("\(#function, privacy: .public) - failed at (2)")
return nil
}
let downloadUrl = metadata["downloadURL"] != nil ? metadata["downloadURL"]![0] : updateUrl
@@ -1693,7 +1630,7 @@ func popupUpdateSingle(_ filename: String, _ url: String, _ subframeUrls: [Strin
let remoteFileContents = getRemoteFileContents(downloadUrl),
((try? remoteFileContents.write(to: fileUrl, atomically: false, encoding: .utf8)) != nil)
else {
- logger.error("\(#function, privacy: .public) - failed at (3)")
+ logger?.error("\(#function, privacy: .public) - failed at (3)")
return nil
}
guard
@@ -1703,7 +1640,7 @@ func popupUpdateSingle(_ filename: String, _ url: String, _ subframeUrls: [Strin
purgeManifest(files),
let matches = getPopupMatches(url, subframeUrls)
else {
- logger.error("\(#function, privacy: .public) - failed at (4)")
+ logger?.error("\(#function, privacy: .public) - failed at (4)")
return nil
}
return matches
@@ -1712,7 +1649,7 @@ func popupUpdateSingle(_ filename: String, _ url: String, _ subframeUrls: [Strin
// page
func getInitData() -> [String: Any]? {
guard let saveLocation = getSaveLocation() else {
- logger.error("\(#function, privacy: .public) - failed at (1)")
+ logger?.error("\(#function, privacy: .public) - failed at (1)")
return nil
}
return [
@@ -1733,7 +1670,7 @@ func saveFile(_ item: [String: Any],_ content: String) -> [String: Any] {
var response = [String: Any]()
let newContent = content
guard let saveLocation = getSaveLocation() else {
- logger.error("\(#function, privacy: .public) - failed at (1)")
+ logger?.error("\(#function, privacy: .public) - failed at (1)")
return ["error": "failed to get save location when attempting to save"]
}
guard
@@ -1798,7 +1735,7 @@ func saveFile(_ item: [String: Any],_ content: String) -> [String: Any] {
do {
try newContent.write(to: newFileUrl, atomically: false, encoding: .utf8)
} catch {
- logger.error("\(#function, privacy: .public) - failed at (2)")
+ logger?.error("\(#function, privacy: .public) - failed at (2)")
return ["error": "failed to write file to disk"]
}
@@ -1808,7 +1745,7 @@ func saveFile(_ item: [String: Any],_ content: String) -> [String: Any] {
guard
let dateMod = try? FileManager.default.attributesOfItem(atPath: newFileUrl.path)[.modificationDate] as? Date
else {
- logger.error("\(#function, privacy: .public) - failed at (3)")
+ logger?.error("\(#function, privacy: .public) - failed at (3)")
return ["error": "failed to read modified date in save function"]
}
@@ -1831,7 +1768,7 @@ func saveFile(_ item: [String: Any],_ content: String) -> [String: Any] {
updateManifestDeclarativeNetRequests(allFiles),
purgeManifest(allFiles)
else {
- logger.error("\(#function, privacy: .public) - failed at (4)")
+ logger?.error("\(#function, privacy: .public) - failed at (4)")
return ["error": "file save but manifest couldn't be updated"]
}
@@ -1864,7 +1801,7 @@ func trashFile(_ item: [String: Any]) -> Bool {
let saveLocation = getSaveLocation(),
let filename = item["filename"] as? String
else {
- logger.error("\(#function, privacy: .public) - failed at (1)")
+ logger?.error("\(#function, privacy: .public) - failed at (1)")
return false
}
// security scope
@@ -1878,13 +1815,13 @@ func trashFile(_ item: [String: Any]) -> Bool {
do {
try FileManager.default.trashItem(at: url, resultingItemURL: nil)
} catch {
- logger.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)")
return false
}
}
// update manifest
guard updateManifestMatches(), updateManifestRequired(), purgeManifest() else {
- logger.error("\(#function, privacy: .public) - failed at (2)")
+ logger?.error("\(#function, privacy: .public) - failed at (2)")
return false
}
return true;
@@ -1945,11 +1882,11 @@ func getFileRemoteUpdate(_ content: String) -> [String: String] {
// background
func nativeChecks() -> [String: String] {
- logger.info("\(#function, privacy: .public) - started")
+ logger?.info("\(#function, privacy: .public) - started")
#if os(iOS)
// check the save location is set
guard (getSaveLocation() != nil) else {
- logger.error("\(#function, privacy: .public) - save location unset (iOS)")
+ logger?.error("\(#function, privacy: .public) - save location unset (iOS)")
return [
"error": "Native checks error (0)",
"saveLocation": "unset",
@@ -1959,41 +1896,41 @@ func nativeChecks() -> [String: String] {
#endif
// check the default directories
guard checkDefaultDirectories() else {
- logger.error("\(#function, privacy: .public) - checkDefaultDirectories failed")
+ logger?.error("\(#function, privacy: .public) - checkDefaultDirectories failed")
return ["error": "Native checks error (1)"]
}
// check the settings
guard checkSettings() else {
- logger.error("\(#function, privacy: .public) - checkSettings failed")
+ logger?.error("\(#function, privacy: .public) - checkSettings failed")
return ["error": "Native checks error (2)"]
}
// get all files to pass as arguments to function below
guard let allFiles = getAllFiles() else {
- logger.error("\(#function, privacy: .public) - getAllFiles failed")
+ logger?.error("\(#function, privacy: .public) - getAllFiles failed")
return ["error": "Native checks error (3)"]
}
// purge the manifest of old records
guard purgeManifest(allFiles) else {
- logger.error("\(#function, privacy: .public) - purgeManifest failed")
+ logger?.error("\(#function, privacy: .public) - purgeManifest failed")
return ["error": "Native checks error (4)"]
}
// update matches in manifest
guard updateManifestMatches(allFiles) else {
- logger.error("\(#function, privacy: .public) - updateManifestMatches failed")
+ logger?.error("\(#function, privacy: .public) - updateManifestMatches failed")
return ["error": "Native checks error (5)"]
}
// update the required resources
guard updateManifestRequired(allFiles) else {
- logger.error("\(#function, privacy: .public) - updateManifestRequired failed")
+ logger?.error("\(#function, privacy: .public) - updateManifestRequired failed")
return ["error": "Native checks error (6)"]
}
// update declarativeNetRequest
guard updateManifestDeclarativeNetRequests(allFiles) else {
- logger.error("\(#function, privacy: .public) - updateManifestDeclarativeNetRequests failed")
+ logger?.error("\(#function, privacy: .public) - updateManifestDeclarativeNetRequests failed")
return ["error": "Native checks error (7)"]
}
// pass some info in response
- logger.info("\(#function, privacy: .public) - complete")
+ logger?.info("\(#function, privacy: .public) - completed")
return ["success": "Native checks complete"]
}
@@ -2002,7 +1939,7 @@ func installCheck(_ content: String) -> [String: Any] {
// this func checks a userscript's metadata to determine if it's already installed
guard let files = getAllFiles() else {
- logger.error("\(#function, privacy: .public) - failed at (1)")
+ logger?.error("\(#function, privacy: .public) - failed at (1)")
return ["error": "installCheck failed at (1)"]
}
@@ -2054,7 +1991,7 @@ func installUserscript(_ content: String) -> [String: Any] {
let metadata = parsed["metadata"] as? [String: [String]],
let n = metadata["name"]?[0]
else {
- logger.error("\(#function, privacy: .public) - failed at (1)")
+ logger?.error("\(#function, privacy: .public) - failed at (1)")
return ["error": "installUserscript failed at (1)"]
}
let name = sanitize(n)
diff --git a/xcode/Safari-Extension/Info.plist b/xcode/Safari-Extension/Info.plist
index eda50006..2cc72fe3 100644
--- a/xcode/Safari-Extension/Info.plist
+++ b/xcode/Safari-Extension/Info.plist
@@ -33,6 +33,8 @@
$(MAC_SHARED_GID)
US_SHARED_GID-iphoneos
$(IOS_SHARED_GID)
+ US_EXT_IDENTIFIER
+ $(EXT_IDENTIFIER)
US_URL_SCHEME
$(APP_URL_SCHEME)
diff --git a/xcode/Safari-Extension/Mac.entitlements b/xcode/Safari-Extension/Mac.entitlements
index f123ee4d..aa2f466a 100644
--- a/xcode/Safari-Extension/Mac.entitlements
+++ b/xcode/Safari-Extension/Mac.entitlements
@@ -8,8 +8,6 @@
$(MAC_SHARED_GID)
- com.apple.security.files.bookmarks.app-scope
-
com.apple.security.files.user-selected.read-write
com.apple.security.network.client
diff --git a/xcode/Safari-Extension/SafariWebExtensionHandler.swift b/xcode/Safari-Extension/SafariWebExtensionHandler.swift
index d6bb4afc..c8e93769 100644
--- a/xcode/Safari-Extension/SafariWebExtensionHandler.swift
+++ b/xcode/Safari-Extension/SafariWebExtensionHandler.swift
@@ -3,15 +3,15 @@ import os
class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
func beginRequest(with context: NSExtensionContext) {
- let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: #fileID)
+ let logger = USLogger(#fileID)
let item = context.inputItems[0] as? NSExtensionItem
let message = item?.userInfo?[SFExtensionMessageKey] as? [String: Any]
// if message received without name, ignore
guard let name = message?["name"] as? String else {
- logger.error("\(#function, privacy: .public) - could not get message name from web extension")
+ logger?.error("\(#function, privacy: .public) - could not get message name from web extension")
return
}
- logger.info("\(#function, privacy: .public) - Got message with name: \(name, privacy: .public)")
+ logger?.info("\(#function, privacy: .public) - Got message with name: \(name, privacy: .public)")
// got a valid message, construct response based on message received
let response = NSExtensionItem()
// send standard error when there's an issue parsing inbound message
diff --git a/xcode/Shared/Preferences.swift b/xcode/Shared/Preferences.swift
new file mode 100644
index 00000000..2ca9c634
--- /dev/null
+++ b/xcode/Shared/Preferences.swift
@@ -0,0 +1,306 @@
+import Foundation
+import os
+
+private let logger = USLogger(#fileID)
+private let suiteName = Bundle.main.infoDictionary!["US_SHARED_GID"] as! String
+private let SDefaults = UserDefaults(suiteName: suiteName) // Shared UserDefaults
+
+let bundleIdentifier = Bundle.main.bundleIdentifier!
+let extIdentifier = Bundle.main.infoDictionary?["US_EXT_IDENTIFIER"] as! String
+
+func getDefaultScriptsDirectoryUrl() -> URL {
+ var url: URL
+ if #available(macOS 13.0, iOS 16.0, *) {
+ url = getDocumentsDirectory().appending(path: "scripts")
+ } else {
+ url = getDocumentsDirectory().appendingPathComponent("scripts")
+ }
+ // if not in safari extension environment, replace with extension path
+ if bundleIdentifier != extIdentifier {
+ var path: String
+ if #available(macOS 13.0, iOS 16.0, *) {
+ path = url.path(percentEncoded: false)
+ } else {
+ path = url.path.removingPercentEncoding ?? url.path
+ }
+ let truePath = path.replacingOccurrences(of: bundleIdentifier, with: extIdentifier)
+ url = URL(fileURLWithPath: truePath, isDirectory: true)
+ }
+ return url
+}
+
+// https://docs.swift.org/swift-book/documentation/the-swift-programming-language/properties#Property-Wrappers
+// https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics/
+@propertyWrapper
+private struct SharedUserDefaults {
+ private let key: String
+ private let defaultValue: T
+
+ init(wrappedValue: T, _ key: String) {
+ self.key = key
+ self.defaultValue = wrappedValue
+ SDefaults?.register(defaults: [key: wrappedValue])
+ }
+
+ var wrappedValue: T {
+ get { SDefaults?.object(forKey: key) as? T ?? defaultValue }
+ set { SDefaults?.set(newValue, forKey: key) }
+ }
+}
+
+#if os(macOS)
+/* https://developer.apple.com/documentation/security/app_sandbox/accessing_files_from_the_macos_app_sandbox
+ # Design ideas and processes
+ Since the security-scoped URL bookmark is only available in the current app,
+ pass to extensions can only use URL bookmark with an implicit security scope.
+ app set URL write to:
+ - `_SetterId` (setter app bundleIdentifier)
+ - `_Transfer` (with an implicit security scope)
+ - `_SecurityScoped_` (with an explicit security scope)
+ app get URL read from: `_SecurityScoped_`
+ ext get URL read flow:
+ - if `_Transfer` exist, remove to `_SecurityScoped_`
+ - if `_Transfer` empty, read from `_SecurityScoped_`
+ */
+@propertyWrapper
+private struct SecurityScopedBookmark {
+ private let key: String
+ private let keySetter: String
+ private let keyTransfer: String
+ private let keySecurityScoped: String
+ private let defaultValue: URL
+
+ init(wrappedValue: URL, _ key: String) {
+ self.key = key
+ self.keySetter = key + "/SetterId"
+ self.keyTransfer = key + "/Transfer"
+ self.keySecurityScoped = key + "/SecurityScoped/" + bundleIdentifier
+ self.defaultValue = wrappedValue
+ }
+
+ private func createBookmark(_ url: URL, _ withSecurityScope: Bool) -> Data? {
+ do {
+ return try url.bookmarkData(
+ options: withSecurityScope ? .withSecurityScope : [],
+ includingResourceValuesForKeys: nil,
+ relativeTo: nil
+ )
+ } catch {
+ logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)")
+ }
+ return nil
+ }
+
+ private func resolvBookmark(_ data: Data, _ withSecurityScope: Bool, _ isStale: inout Bool) -> URL? {
+ do {
+ return try URL(
+ resolvingBookmarkData: data,
+ options: withSecurityScope ? .withSecurityScope : [],
+ relativeTo: nil,
+ bookmarkDataIsStale: &isStale
+ )
+ } catch {
+ logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)")
+ }
+ return nil
+ }
+
+ private func updateBookmark() {
+ guard let setterIdentifier = SDefaults?.string(forKey: keySetter) else {
+ logger?.debug("\(#function, privacy: .public) - setterId not exist: \(key, privacy: .public)")
+ return
+ }
+ guard bundleIdentifier != setterIdentifier else { return } // update only in non-setter environment
+ guard let data = SDefaults?.data(forKey: keyTransfer) else { // no need to update due empty data
+ logger?.debug("\(#function, privacy: .public) - no update: \(key, privacy: .public)")
+ return
+ }
+ var isStale = false // no need to renew since will remove anyway
+ guard let url = resolvBookmark(data, false, &isStale) else { // get URL bookmark with an implicit security scope
+ removeBookmark()
+ return
+ }
+ defer { url.stopAccessingSecurityScopedResource() } // revoke implicitly starts security-scoped access
+ if let data = createBookmark(url, true) { // set URL bookmark with an explicit security scope
+ logger?.info("\(#function, privacy: .public) - update bookmark: \(key, privacy: .public) \(url, privacy: .public)")
+ SDefaults?.removeObject(forKey: keyTransfer)
+ SDefaults?.set(data, forKey: keySecurityScoped)
+ }
+ }
+
+ private func removeBookmark() {
+ logger?.info("\(#function, privacy: .public) - remove invalid bookmark: \(key, privacy: .public)")
+ SDefaults?.removeObject(forKey: keyTransfer)
+ SDefaults?.removeObject(forKey: keySecurityScoped)
+ if let setterIdentifier = SDefaults?.string(forKey: keySetter) {
+ SDefaults?.removeObject(forKey: key + "/SecurityScoped/" + setterIdentifier)
+ }
+ }
+
+ // NOTE: This function will be deleted after several public versions of v4.5.0
+ private func legacyBookmarkImporter() {
+ guard key == "ScriptsDirectoryUrlBookmarkData" else { return }
+ guard bundleIdentifier == extIdentifier else { return }
+ func legacyCleanup() {
+ logger?.debug("\(#function, privacy: .public) - cleanup legacy bookmark data")
+ SDefaults?.removeObject(forKey: "hostSelectedSaveLocation") // shared
+ UserDefaults.standard.removeObject(forKey: "saveLocation") // ext
+ UserDefaults.standard.removeObject(forKey: "userSaveLocation") // ext
+ }
+ guard let data = UserDefaults.standard.data(forKey: "userSaveLocation") else { // get from old key
+ logger?.debug("\(#function, privacy: .public) - legacy bookmark not exist")
+ return
+ }
+ guard SDefaults?.string(forKey: keySetter) == nil else { // already a new key
+ return legacyCleanup()
+ }
+ logger?.debug("\(#function, privacy: .public) - Import legacy bookmark data")
+ var isStale = false // no need to renew since will remove anyway
+ guard let url = resolvBookmark(data, true, &isStale) else { // get URL bookmark with an explicit security scope
+ return legacyCleanup()
+ }
+ guard url.startAccessingSecurityScopedResource() else {
+ return legacyCleanup()
+ }
+ defer { url.stopAccessingSecurityScopedResource() }
+ guard let transferData = createBookmark(url, false) else {
+ return legacyCleanup()
+ }
+ SDefaults?.set(bundleIdentifier, forKey: keySetter)
+ SDefaults?.set(transferData, forKey: keyTransfer) // set URL bookmark with an implicit security scope
+ SDefaults?.set(data, forKey: keySecurityScoped) // set URL bookmark with an explicit security scope
+ legacyCleanup()
+ }
+
+ var wrappedValue: URL {
+ get {
+ legacyBookmarkImporter()
+ updateBookmark()
+ logger?.info("\(#function, privacy: .public) - try get bookmark: \(key, privacy: .public)")
+ guard let data = SDefaults?.data(forKey: keySecurityScoped) else {
+ logger?.debug("\(#function, privacy: .public) - bookmark not exist: \(key, privacy: .public)")
+ return defaultValue
+ }
+ var isStale = false
+ guard let url = resolvBookmark(data, true, &isStale) else { // get URL bookmark with an explicit security scope
+ removeBookmark() // remove data that cannot be resolved
+ return defaultValue
+ }
+ if isStale, url.startAccessingSecurityScopedResource() { // renew URL bookmark
+ defer { url.stopAccessingSecurityScopedResource() }
+ if let data = createBookmark(url, true) { // set URL bookmark with an explicit security scope
+ logger?.info("\(#function, privacy: .public) - renew bookmark: \(key, privacy: .public) \(url, privacy: .public)")
+ SDefaults?.set(data, forKey: keySecurityScoped)
+ }
+ }
+ return url
+ }
+ set(url) {
+ let k = key // key cannot be log directly with error: Escaping autoclosure captures mutating 'self' parameter
+ logger?.info("\(#function, privacy: .public) - try set bookmark: \(k, privacy: .public) \(url, privacy: .public)")
+ guard let tdata = createBookmark(url, false), let sdata = createBookmark(url, true) else {
+ logger?.info("\(#function, privacy: .public) - failed create bookmark: \(k, privacy: .public) \(url, privacy: .public)")
+ return
+ }
+ SDefaults?.set(bundleIdentifier, forKey: keySetter)
+ SDefaults?.set(tdata, forKey: keyTransfer) // set URL bookmark with an implicit security scope
+ SDefaults?.set(sdata, forKey: keySecurityScoped) // set URL bookmark with an explicit security scope
+ }
+ }
+}
+#elseif os(iOS)
+/* https://developer.apple.com/documentation/uikit/view_controllers/providing_access_to_directories#3331285
+ iOS and macOS have completely different processes, iOS has no implicit security scope
+ The security-scoped URL is not bind to the app and can be shared directly between the app and ext
+ Separating the wrappers for the two platforms is intentional for ease of viewing code logic
+ */
+@propertyWrapper
+private struct SecurityScopedBookmark {
+ private let key: String
+ private let defaultValue: URL
+
+ init(wrappedValue: URL, _ key: String) {
+ self.key = key
+ self.defaultValue = wrappedValue
+ }
+
+ private func createBookmark(_ url: URL) -> Data? {
+ do {
+ return try url.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil)
+ } catch {
+ logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)")
+ }
+ return nil
+ }
+
+ private func resolvBookmark(_ data: Data, _ isStale: inout Bool) -> URL? {
+ do {
+ return try URL(resolvingBookmarkData: data, bookmarkDataIsStale: &isStale)
+ } catch {
+ logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)")
+ }
+ return nil
+ }
+
+ // NOTE: This function will be deleted after several public versions of v4.5.0
+ private func legacyBookmarkImporter() {
+ guard let data = SDefaults?.data(forKey: "iosReadLocation") else { // get from old key
+ logger?.debug("\(#function, privacy: .public) - legacy bookmark not exist")
+ return
+ }
+ logger?.debug("\(#function, privacy: .public) - Import legacy bookmark data")
+ SDefaults?.set(data, forKey: key)
+ SDefaults?.removeObject(forKey: "iosReadLocation")
+ }
+
+ var wrappedValue: URL {
+ get {
+ legacyBookmarkImporter()
+ logger?.info("\(#function, privacy: .public) - try get bookmark: \(key, privacy: .public)")
+ guard let data = SDefaults?.data(forKey: key) else {
+ logger?.debug("\(#function, privacy: .public) - bookmark not exist: \(key, privacy: .public)")
+ return defaultValue
+ }
+ var isStale = false
+ guard let url = resolvBookmark(data, &isStale) else { // get security-scoped URL
+ SDefaults?.removeObject(forKey: key) // remove data that cannot be resolved
+ return defaultValue
+ }
+ if isStale, url.startAccessingSecurityScopedResource() { // renew URL bookmark
+ defer { url.stopAccessingSecurityScopedResource() }
+ if let data = createBookmark(url) { // set security-scoped URL
+ logger?.info("\(#function, privacy: .public) - renew bookmark: \(key, privacy: .public) \(url, privacy: .public)")
+ SDefaults?.set(data, forKey: key)
+ }
+ }
+ return url
+ }
+ set(url) {
+ let k = key // key cannot be log directly with error: Escaping autoclosure captures mutating 'self' parameter
+ logger?.info("\(#function, privacy: .public) - try set bookmark: \(k, privacy: .public) \(url, privacy: .public)")
+ let didStartAccessing = url.startAccessingSecurityScopedResource()
+ defer {
+ if didStartAccessing { url.stopAccessingSecurityScopedResource() }
+ }
+ guard let data = createBookmark(url) else {
+ logger?.info("\(#function, privacy: .public) - failed create bookmark: \(k, privacy: .public) \(url, privacy: .public)")
+ return
+ }
+ SDefaults?.set(data, forKey: key) // set security-scoped URL
+ }
+ }
+}
+#endif
+
+// Define shared preferences, default values ​​determine data type
+struct Preferences {
+/* Example preference, can be get or set with: Preferences.propertyName
+ @SharedUserDefaults("SharedUserDefaultsKeyName")
+ static var propertyName = "DefaultValue" // String
+*/
+ @SecurityScopedBookmark("ScriptsDirectoryUrlBookmarkData")
+ static var scriptsDirectoryUrl = getDefaultScriptsDirectoryUrl()
+
+ @SharedUserDefaults("EnableLogger")
+ static var enableLogger = false
+}
diff --git a/xcode/Shared/Shared.swift b/xcode/Shared/Shared.swift
index 7f2c4fb8..d07a5667 100644
--- a/xcode/Shared/Shared.swift
+++ b/xcode/Shared/Shared.swift
@@ -2,16 +2,16 @@ import Foundation
import SafariServices
import os
-fileprivate let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: #fileID)
-
-struct SharedDefaults {
- // https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_application-groups
- static let suiteName = Bundle.main.infoDictionary?["US_SHARED_GID"] as? String
- #if os(iOS)
- static let keyName = "iosReadLocation"
- #elseif os(macOS)
- static let keyName = "hostSelectedSaveLocation"
- #endif
+func USLogger(_ category: String) -> Logger? {
+ let subsystem = Bundle.main.bundleIdentifier!
+#if DEBUG // Always enable logger in DEBUG builds
+ return Logger(subsystem: subsystem, category: category)
+#else
+ if Preferences.enableLogger {
+ return Logger(subsystem: subsystem, category: category)
+ }
+ return nil
+#endif
}
func getDocumentsDirectory() -> URL {
@@ -42,60 +42,3 @@ func getPlatform() -> String {
#endif
return platform
}
-
-func saveBookmark(url: URL, isShared: Bool, keyName: String, isSecure: Bool) -> Bool {
- #if os(iOS)
- let options:URL.BookmarkCreationOptions = []
- #elseif os(macOS)
- let options:URL.BookmarkCreationOptions = isSecure ? [.withSecurityScope] : []
- #endif
- do {
- let bookmark = try url.bookmarkData(
- options: options,
- includingResourceValuesForKeys: nil,
- relativeTo: nil
- )
- #if os(iOS)
- UserDefaults(suiteName: SharedDefaults.suiteName)?.set(bookmark, forKey: keyName)
- #elseif os(macOS)
- if isShared {
- UserDefaults(suiteName: SharedDefaults.suiteName)?.set(bookmark, forKey: keyName)
- } else {
- UserDefaults.standard.set(bookmark, forKey: keyName)
- }
- #endif
- return true
- } catch let error {
- logger.error("\(#function, privacy: .public) - \(error, privacy: .public)")
- return false
- }
-}
-
-func readBookmark(data: Data, isSecure: Bool) -> URL? {
- #if os(iOS)
- let options:URL.BookmarkResolutionOptions = []
- #elseif os(macOS)
- let options:URL.BookmarkResolutionOptions = isSecure ? [.withSecurityScope] : []
- #endif
- do {
- var bookmarkIsStale = false
- let url = try URL(
- resolvingBookmarkData: data,
- options: options,
- relativeTo: nil,
- bookmarkDataIsStale: &bookmarkIsStale
- )
- if bookmarkIsStale {
- NSLog("Stale bookmark, renewing it \(url)")
- if saveBookmark(url: url, isShared: true, keyName: SharedDefaults.keyName, isSecure: false) {
- NSLog("Successfully renewed stale bookmark - \(url)")
- } else {
- NSLog("Could not renew stale bookmark - \(url)")
- }
- }
- return url
- } catch let error {
- logger.error("\(#function, privacy: .public) - \(error, privacy: .public)")
- return nil
- }
-}
diff --git a/xcode/Userscripts.xcodeproj/project.pbxproj b/xcode/Userscripts.xcodeproj/project.pbxproj
index 8286d541..1f7f2371 100644
--- a/xcode/Userscripts.xcodeproj/project.pbxproj
+++ b/xcode/Userscripts.xcodeproj/project.pbxproj
@@ -7,9 +7,15 @@
objects = {
/* Begin PBXBuildFile section */
+ 030C64F62AC62CC900548FBD /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 030C64F52AC62CC900548FBD /* Settings.bundle */; };
0336619F294DF7C900CFE179 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0336619E294DF7C900CFE179 /* Functions.swift */; };
033661A529510B7900CFE179 /* View.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 033661A329510B7900CFE179 /* View.storyboard */; };
0376905B29808EEA00F3474D /* background.js in Resources */ = {isa = PBXBuildFile; fileRef = 0376905A29808EEA00F3474D /* background.js */; };
+ 03C24F972ABD2CBB00F130F9 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03C24F962ABD2CBB00F130F9 /* Preferences.swift */; };
+ 03C24F982ABD2CBB00F130F9 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03C24F962ABD2CBB00F130F9 /* Preferences.swift */; };
+ 03C24F992ABD2CBB00F130F9 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03C24F962ABD2CBB00F130F9 /* Preferences.swift */; };
+ 03C24F9A2ABD2CBB00F130F9 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03C24F962ABD2CBB00F130F9 /* Preferences.swift */; };
+ 03C24F9B2ABD2CBB00F130F9 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03C24F962ABD2CBB00F130F9 /* Preferences.swift */; };
03E20D06291E170000B4F692 /* dist in Resources */ = {isa = PBXBuildFile; fileRef = 03E20D05291E170000B4F692 /* dist */; };
03E20D07291E170000B4F692 /* dist in Resources */ = {isa = PBXBuildFile; fileRef = 03E20D05291E170000B4F692 /* dist */; };
4A143AAC279DE6FF0029BFD0 /* UserscriptsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A143AAB279DE6FF0029BFD0 /* UserscriptsTests.swift */; };
@@ -99,9 +105,11 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 030C64F52AC62CC900548FBD /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; };
0336619E294DF7C900CFE179 /* Functions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Functions.swift; sourceTree = ""; };
033661A429510B7900CFE179 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/View.storyboard; sourceTree = ""; };
0376905A29808EEA00F3474D /* background.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = background.js; sourceTree = ""; };
+ 03C24F962ABD2CBB00F130F9 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; };
03E20D05291E170000B4F692 /* dist */ = {isa = PBXFileReference; lastKnownFileType = folder; path = dist; sourceTree = ""; };
4A143AA9279DE6FF0029BFD0 /* Mac-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Mac-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
4A143AAB279DE6FF0029BFD0 /* UserscriptsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserscriptsTests.swift; sourceTree = ""; };
@@ -196,6 +204,7 @@
children = (
4A4CF6F2270A38BF00111584 /* Info.plist */,
4A4CF718270A38C700111584 /* App.entitlements */,
+ 030C64F52AC62CC900548FBD /* Settings.bundle */,
4A4CF6DF270A38BD00111584 /* Resources */,
4A4CF6F1270A38BF00111584 /* Assets.xcassets */,
4A4CF6DD270A38BD00111584 /* AppDelegate.swift */,
@@ -259,12 +268,12 @@
4A57BA06227235CE008A9763 /* Safari-Extension */ = {
isa = PBXGroup;
children = (
- 4AF1DD0D268265DB00086E88 /* Resources */,
- 4A57BA07227235CE008A9763 /* SafariWebExtensionHandler.swift */,
- 4AED6491268CDB58001794BF /* Functions.swift */,
4A57BA0E227235CE008A9763 /* Info.plist */,
4A57BA1D227235E6008A9763 /* Mac.entitlements */,
7878ED2D299BA40800E36A24 /* iOS.entitlements */,
+ 4AF1DD0D268265DB00086E88 /* Resources */,
+ 4A57BA07227235CE008A9763 /* SafariWebExtensionHandler.swift */,
+ 4AED6491268CDB58001794BF /* Functions.swift */,
);
path = "Safari-Extension";
sourceTree = "";
@@ -300,6 +309,7 @@
isa = PBXGroup;
children = (
78B25582299D4C27000B2E9B /* Shared.swift */,
+ 03C24F962ABD2CBB00F130F9 /* Preferences.swift */,
);
path = Shared;
sourceTree = "";
@@ -333,6 +343,7 @@
4A4CF6D8270A38BD00111584 /* Frameworks */,
4A4CF6D9270A38BD00111584 /* Resources */,
4A4CF713270A38BF00111584 /* Embed Foundation Extensions */,
+ 030C64F72AC713A600548FBD /* Update Settings.bundle */,
);
buildRules = (
);
@@ -468,6 +479,7 @@
4A4CF6F0270A38BD00111584 /* Main.storyboard in Resources */,
4A4CF6ED270A38BD00111584 /* LaunchScreen.storyboard in Resources */,
4A4CF6E2270A38BD00111584 /* Main.html in Resources */,
+ 030C64F62AC62CC900548FBD /* Settings.bundle in Resources */,
4A4CF710270A38BF00111584 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -510,6 +522,30 @@
};
/* End PBXResourcesBuildPhase section */
+/* Begin PBXShellScriptBuildPhase section */
+ 030C64F72AC713A600548FBD /* Update Settings.bundle */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "$(SRCROOT)/$(TARGET_NAME)/Settings.bundle/Root.plist",
+ "$(TARGET_BUILD_DIR)/$(INFOPLIST_PATH)",
+ );
+ name = "Update Settings.bundle";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(TARGET_BUILD_DIR)/$(EXECUTABLE_FOLDER_PATH)/Settings.bundle/Root.plist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "# Dynamically introduce US_SHARED_GID in xcconfig for Settings.bundle\nUS_SHARED_GID=$(/usr/libexec/PlistBuddy -c \"Print US_SHARED_GID\" \"$TARGET_BUILD_DIR/$INFOPLIST_PATH\")\nSETTINGS_BUNDLE=$TARGET_BUILD_DIR/$EXECUTABLE_FOLDER_PATH/Settings.bundle/Root.plist\n/usr/libexec/PlistBuddy -c \"Set ApplicationGroupContainerIdentifier $US_SHARED_GID\" \"$SETTINGS_BUNDLE\"\n";
+ };
+/* End PBXShellScriptBuildPhase section */
+
/* Begin PBXSourcesBuildPhase section */
4A143AA5279DE6FF0029BFD0 /* Sources */ = {
isa = PBXSourcesBuildPhase;
@@ -517,6 +553,7 @@
files = (
7878ED24299BA27B00E36A24 /* SafariWebExtensionHandler.swift in Sources */,
4A143AB2279DEA170029BFD0 /* Functions.swift in Sources */,
+ 03C24F9B2ABD2CBB00F130F9 /* Preferences.swift in Sources */,
78B25587299D4C27000B2E9B /* Shared.swift in Sources */,
4A143AAC279DE6FF0029BFD0 /* UserscriptsTests.swift in Sources */,
);
@@ -528,6 +565,7 @@
files = (
4A4CF6EA270A38BD00111584 /* ViewController.swift in Sources */,
4A4CF6DE270A38BD00111584 /* AppDelegate.swift in Sources */,
+ 03C24F992ABD2CBB00F130F9 /* Preferences.swift in Sources */,
78B25585299D4C27000B2E9B /* Shared.swift in Sources */,
4A4CF6E8270A38BD00111584 /* SceneDelegate.swift in Sources */,
);
@@ -539,6 +577,7 @@
files = (
78B25586299D4C27000B2E9B /* Shared.swift in Sources */,
4A301B2A270A474400C7E9E1 /* SafariWebExtensionHandler.swift in Sources */,
+ 03C24F9A2ABD2CBB00F130F9 /* Preferences.swift in Sources */,
4A301B32270A49F200C7E9E1 /* Functions.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -549,6 +588,7 @@
files = (
4A57B9F7227235CD008A9763 /* ViewController.swift in Sources */,
4A57B9F2227235CD008A9763 /* AppDelegate.swift in Sources */,
+ 03C24F972ABD2CBB00F130F9 /* Preferences.swift in Sources */,
78B25583299D4C27000B2E9B /* Shared.swift in Sources */,
0336619F294DF7C900CFE179 /* Functions.swift in Sources */,
);
@@ -560,6 +600,7 @@
files = (
78B25584299D4C27000B2E9B /* Shared.swift in Sources */,
4A57BA08227235CE008A9763 /* SafariWebExtensionHandler.swift in Sources */,
+ 03C24F982ABD2CBB00F130F9 /* Preferences.swift in Sources */,
4AED6492268CDB58001794BF /* Functions.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/xcode/iOS-App/AppDelegate.swift b/xcode/iOS-App/AppDelegate.swift
index e1fe4a8a..8bb92da4 100644
--- a/xcode/iOS-App/AppDelegate.swift
+++ b/xcode/iOS-App/AppDelegate.swift
@@ -7,6 +7,7 @@
//
import UIKit
+import os
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
@@ -15,6 +16,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
+ let logger = USLogger(#fileID)
+ // set the scripts directory to the app document on first use
+ if Preferences.scriptsDirectoryUrl == getDefaultScriptsDirectoryUrl() {
+ logger?.info("\(#function, privacy: .public) - Initialize default directory")
+ Preferences.scriptsDirectoryUrl = getDocumentsDirectory()
+ }
return true
}
diff --git a/xcode/iOS-App/Base.lproj/Main.html b/xcode/iOS-App/Base.lproj/Main.html
index e718ab5a..bfd9ae28 100644
--- a/xcode/iOS-App/Base.lproj/Main.html
+++ b/xcode/iOS-App/Base.lproj/Main.html
@@ -1 +1 @@
-
You can turn on the Userscripts iOS Safari extension in Settings. Read the docs.
CURRENT DIRECTORY:
init
+
You can turn on the Userscripts iOS Safari extension in Settings. Read the docs.
CURRENT DIRECTORY:
init
diff --git a/xcode/iOS-App/Info.plist b/xcode/iOS-App/Info.plist
index a5539a85..b6d4f637 100644
--- a/xcode/iOS-App/Info.plist
+++ b/xcode/iOS-App/Info.plist
@@ -38,7 +38,13 @@
UIStatusBarStyleLightContent
UIViewControllerBasedStatusBarAppearance
+ UIFileSharingEnabled
+
+ LSSupportsOpeningDocumentsInPlace
+
US_SHARED_GID
$(IOS_SHARED_GID)
+ US_EXT_IDENTIFIER
+ $(EXT_IDENTIFIER)
diff --git a/xcode/iOS-App/Settings.bundle/Root.plist b/xcode/iOS-App/Settings.bundle/Root.plist
new file mode 100644
index 00000000..b9ad077e
--- /dev/null
+++ b/xcode/iOS-App/Settings.bundle/Root.plist
@@ -0,0 +1,31 @@
+
+
+
+
+ ApplicationGroupContainerIdentifier
+ // US_SHARED_GID will be introduced in the custom build script
+ StringsTable
+ Root
+ PreferenceSpecifiers
+
+
+ Type
+ PSGroupSpecifier
+ Title
+ GroupDebug
+ FooterText
+ FooterText
+
+
+ Type
+ PSToggleSwitchSpecifier
+ Title
+ EnableLogger
+ Key
+ EnableLogger
+ DefaultValue
+
+
+
+
+
diff --git a/xcode/iOS-App/Settings.bundle/en.lproj/Root.strings b/xcode/iOS-App/Settings.bundle/en.lproj/Root.strings
new file mode 100644
index 00000000..dc826ef0
Binary files /dev/null and b/xcode/iOS-App/Settings.bundle/en.lproj/Root.strings differ
diff --git a/xcode/iOS-App/ViewController.swift b/xcode/iOS-App/ViewController.swift
index f1a3347c..d7734b76 100644
--- a/xcode/iOS-App/ViewController.swift
+++ b/xcode/iOS-App/ViewController.swift
@@ -11,7 +11,7 @@ import WebKit
import UniformTypeIdentifiers
import os
-fileprivate let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: #fileID)
+private let logger = USLogger(#fileID)
class ViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler, UIDocumentPickerDelegate {
@@ -33,17 +33,7 @@ class ViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHan
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "??"
let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "??"
- var readLocation:String
- if let sharedBookmarkData = UserDefaults(suiteName: SharedDefaults.suiteName)?.data(forKey: SharedDefaults.keyName) {
- if let bookmarkUrl = readBookmark(data: sharedBookmarkData, isSecure: true) {
- readLocation = bookmarkUrl.absoluteString
- } else {
- readLocation = "Failed to get read directory"
- }
- } else {
- readLocation = "Select a directory to load userscripts"
- }
- webView.evaluateJavaScript("printDirectory('\(readLocation)')")
+ webView.evaluateJavaScript("printDirectory('\(Preferences.scriptsDirectoryUrl.absoluteString)')")
webView.evaluateJavaScript("printVersion('v\(appVersion)', '(\(buildNumber))')")
}
@@ -64,32 +54,21 @@ class ViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHan
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard let name = message.body as? String else {
- logger.error("\(#function, privacy: .public) - Userscripts iOS received a message without a name")
+ logger?.error("\(#function, privacy: .public) - Userscripts iOS received a message without a name")
return
}
if name == "SET_READ_LOCATION" {
- logger.info("\(#function, privacy: .public) - Userscripts iOS has requested to set the readLocation")
+ // https://developer.apple.com/documentation/uikit/view_controllers/providing_access_to_directories
+ logger?.info("\(#function, privacy: .public) - Userscripts iOS has requested to set the readLocation")
let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.folder])
documentPicker.delegate = self
+ documentPicker.directoryURL = Preferences.scriptsDirectoryUrl
present(documentPicker, animated: true, completion: nil)
}
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
- // https://developer.apple.com/videos/play/wwdc2018/216
- do {
- let shouldStopAccessing = url.startAccessingSecurityScopedResource()
- defer {
- if shouldStopAccessing {url.stopAccessingSecurityScopedResource()}
- }
- if saveBookmark(url: url, isShared: true, keyName: SharedDefaults.keyName, isSecure: false) {
- webView.evaluateJavaScript("printDirectory('\(url.absoluteString)')")
- } else {
- throw NSError(domain: "Failed to saved bookmark", code: 0, userInfo: [:])
- }
- } catch let error {
- logger.error("\(#function, privacy: .public) - \(error, privacy: .public)")
- return
- }
+ Preferences.scriptsDirectoryUrl = url
+ webView.evaluateJavaScript("printDirectory('\(Preferences.scriptsDirectoryUrl.absoluteString)')")
}
}
diff --git a/xcode/xcconfig/Userscripts-Debug.xcconfig b/xcode/xcconfig/Userscripts-Debug.xcconfig
index f7b4cc7f..a0060f16 100644
--- a/xcode/xcconfig/Userscripts-Debug.xcconfig
+++ b/xcode/xcconfig/Userscripts-Debug.xcconfig
@@ -9,7 +9,7 @@ MAC_APP_VERSION = 1.0.0
// Sign to Run Locally
CODE_SIGN_IDENTITY = -
// Organization Identifier
-ORG_IDENTIFIER = debug
+ORG_IDENTIFIER = dev.debug
// Application Identifier
APP_IDENTIFIER = $(ORG_IDENTIFIER).userscripts
// Safari Extension Identifier
diff --git a/xcode/xcconfig/Userscripts-Release.xcconfig b/xcode/xcconfig/Userscripts-Release.xcconfig
index 516d42eb..3869c42d 100644
--- a/xcode/xcconfig/Userscripts-Release.xcconfig
+++ b/xcode/xcconfig/Userscripts-Release.xcconfig
@@ -11,12 +11,13 @@ DEVELOPMENT_TEAM = J74Q8V8V8N
// Organization Identifier
ORG_IDENTIFIER = com.userscripts
// Application Identifier
-APP_IDENTIFIER = com.userscripts.macos
+APP_IDENTIFIER = $(ORG_IDENTIFIER).macos
// Safari Extension Identifier
EXT_IDENTIFIER = $(APP_IDENTIFIER).Userscripts-Extension
// App Groups and Group Containers
-IOS_SHARED_GID = group.com.userscripts.ios
-MAC_SHARED_GID = $(DEVELOPMENT_TEAM).com.userscripts.macos
+// https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_application-groups
+IOS_SHARED_GID = group.$(ORG_IDENTIFIER).ios
+MAC_SHARED_GID = $(DEVELOPMENT_TEAM).$(ORG_IDENTIFIER).macos
// App URL Scheme
APP_URL_SCHEME = userscriptsurlscheme