diff --git a/.github/workflows/deployment.yaml b/.github/workflows/deployment.yaml index 86855b3f..f9b2e529 100644 --- a/.github/workflows/deployment.yaml +++ b/.github/workflows/deployment.yaml @@ -9,7 +9,7 @@ jobs: strategy: matrix: # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs platform: [mac, ios] - runs-on: macos-15 # https://github.com/actions/runner-images + runs-on: macos-26 # https://github.com/actions/runner-images steps: - uses: actions/checkout@v4 # https://github.com/actions/checkout with: @@ -37,9 +37,10 @@ jobs: if: ${{ github.event.release.prerelease }} - run: npm run build:${{ matrix.platform }}-safari-15 if: ${{ ! github.event.release.prerelease }} - - name: Set xcode version - run: sudo xcode-select -s "/Applications/Xcode_26_beta.app" # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-arm64-Readme.md#xcode - - run: xcodebuild -downloadPlatform iOS # Temporary fix for Xcode_26_beta + # - name: Set xcode version + # run: sudo xcode-select -s "/Applications/Xcode_26.1.1.app" # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md#xcode + # - run: xcodebuild -downloadPlatform iOS # Temporary fix for Xcode_26_beta + # if: matrix.platform == 'ios' - name: Run fastlane id: fastlane working-directory: ./fastlane diff --git a/fastlane/Gemfile.lock b/fastlane/Gemfile.lock index a8ba4db2..c6013a88 100644 --- a/fastlane/Gemfile.lock +++ b/fastlane/Gemfile.lock @@ -1,39 +1,40 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.7) - base64 - nkf - rexml - addressable (2.8.7) - public_suffix (>= 2.0.2, < 7.0) + CFPropertyList (3.0.8) + abbrev (0.1.2) + addressable (2.8.8) + public_suffix (>= 2.0.2, < 8.0) artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.4.0) - aws-partitions (1.1125.0) - aws-sdk-core (3.226.2) + aws-partitions (1.1188.0) + aws-sdk-core (3.239.2) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) base64 + bigdecimal jmespath (~> 1, >= 1.6.1) logger - aws-sdk-kms (1.106.0) - aws-sdk-core (~> 3, >= 3.225.0) + aws-sdk-kms (1.118.0) + aws-sdk-core (~> 3, >= 3.239.1) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.192.0) - aws-sdk-core (~> 3, >= 3.225.0) + aws-sdk-s3 (1.205.0) + aws-sdk-core (~> 3, >= 3.234.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) aws-sigv4 (1.12.1) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) - base64 (0.3.0) + base64 (0.2.0) + bigdecimal (3.3.1) claide (1.1.0) colored (1.2) colored2 (3.1.2) commander (4.6.0) highline (~> 2.0.0) + csv (3.3.5) declarative (0.0.20) digest-crc (0.7.0) rake (>= 12.0.0, < 14.0.0) @@ -53,9 +54,9 @@ GEM faraday-rack (~> 1.0) faraday-retry (~> 1.0) ruby2_keywords (>= 0.0.4) - faraday-cookie_jar (0.0.7) + faraday-cookie_jar (0.0.8) faraday (>= 0.8.0) - http-cookie (~> 1.0.0) + http-cookie (>= 1.0.0) faraday-em_http (1.0.0) faraday-em_synchrony (1.0.1) faraday-excon (1.1.0) @@ -70,15 +71,18 @@ GEM faraday_middleware (1.2.1) faraday (~> 1.0) fastimage (2.4.0) - fastlane (2.228.0) + fastlane (2.229.1) CFPropertyList (>= 2.3, < 4.0.0) + abbrev (~> 0.1.2) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) + base64 (~> 0.2.0) bundler (>= 1.12.0, < 3.0.0) colored (~> 1.2) commander (~> 4.6) + csv (~> 3.3) dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 4.0) excon (>= 0.71.0, < 1.0.0) @@ -98,7 +102,9 @@ GEM jwt (>= 2.1.0, < 3) mini_magick (>= 4.9.4, < 5.0.0) multipart-post (>= 2.0.0, < 3.0.0) + mutex_m (~> 0.3.0) naturally (~> 2.2) + nkf (~> 0.2.0) optparse (>= 0.1.1, < 1.0.0) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) @@ -157,37 +163,37 @@ GEM httpclient (2.9.0) mutex_m jmespath (1.6.2) - json (2.12.2) + json (2.16.0) jwt (2.10.2) base64 logger (1.7.0) mini_magick (4.13.2) mini_mime (1.1.5) - multi_json (1.15.0) + multi_json (1.17.0) multipart-post (2.4.1) mutex_m (0.3.0) nanaimo (0.4.0) naturally (2.3.0) nkf (0.2.0) - optparse (0.6.0) + optparse (0.8.0) os (1.1.4) plist (3.7.2) - public_suffix (6.0.2) - rake (13.3.0) + public_suffix (7.0.0) + rake (13.3.1) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.4.1) + rexml (3.4.4) rouge (3.28.0) ruby2_keywords (0.0.5) rubyzip (2.4.1) security (0.1.5) - signet (0.20.0) + signet (0.21.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) - jwt (>= 1.5, < 3.0) + jwt (>= 1.5, < 4.0) multi_json (~> 1.10) simctl (1.6.10) CFPropertyList @@ -217,11 +223,11 @@ GEM xcpretty (~> 0.2, >= 0.0.7) PLATFORMS - arm64-darwin-23 + arm64-darwin-25 ruby DEPENDENCIES fastlane BUNDLED WITH - 2.6.9 + 2.7.2 diff --git a/public/ext/shared/_locales/en/messages.json b/public/ext/shared/_locales/en/messages.json index a39bfc24..c17c6cad 100644 --- a/public/ext/shared/_locales/en/messages.json +++ b/public/ext/shared/_locales/en/messages.json @@ -85,6 +85,24 @@ "settings_global_active_desc": { "message": "Toggle on/off script injection for the pages you visit" }, + "settings_theme_mode": { + "message": "Theme mode" + }, + "settings_theme_mode_desc": { + "message": "Toggle theme mode" + }, + "settings_theme_mode_auto": { + "message": "Auto (System)" + }, + "settings_theme_mode_dark": { + "message": "Dark" + }, + "settings_theme_mode_light": { + "message": "Light" + }, + "settings_theme_mode_custom": { + "message": "Custom" + }, "settings_global_exclude_match": { "message": "Global Exclude Match patterns" }, diff --git a/src/ext/action-popup/App.svelte b/src/ext/action-popup/App.svelte index 5740e7b5..6d593530 100644 --- a/src/ext/action-popup/App.svelte +++ b/src/ext/action-popup/App.svelte @@ -242,6 +242,12 @@ } platform = pltfm.platform; + /* Override theme mode */ + const theme = await settingsStorage.get("theme_mode"); + if (theme && theme !== "auto") { + document.body.style.setProperty("color-scheme", theme); + } + // display native error if there is const errorNative = await settingsStorage.get("error_native"); if (errorNative.error) { diff --git a/src/ext/content-scripts/entry-script-market.js b/src/ext/content-scripts/entry-script-market.js index 67022e47..45ba17d6 100644 --- a/src/ext/content-scripts/entry-script-market.js +++ b/src/ext/content-scripts/entry-script-market.js @@ -9,10 +9,10 @@ async function injection() { ); } for (const link of links) { + if (link["href"]) url = link["href"]; link.addEventListener( "click", (e) => { - url = link["href"]; e.stopImmediatePropagation(); e.preventDefault(); browser.runtime.sendMessage({ name: "WEB_USERJS_POPUP" }); @@ -23,14 +23,27 @@ async function injection() { } async function listeners() { - browser.runtime.onMessage.addListener(async (message) => { + /** + * handle messages from background, popup, etc... + * @type {import("webextension-polyfill").Runtime.OnMessageListener} + */ + const handleMessage = async (message) => { if (import.meta.env.MODE === "development") { console.debug(message, url); } if (message === "TAB_CLICK_USERJS") { - const response = url; - url = undefined; // respond only once of click event - return response; + return url; + } + }; + /** Dynamically remove listeners to avoid memory leaks */ + if (document.visibilityState === "visible") { + browser.runtime.onMessage.addListener(handleMessage); + } + document.addEventListener("visibilitychange", () => { + if (document.hidden) { + browser.runtime.onMessage.removeListener(handleMessage); + } else { + browser.runtime.onMessage.addListener(handleMessage); } }); } diff --git a/src/ext/content-scripts/entry-userscripts.js b/src/ext/content-scripts/entry-userscripts.js index 5721dd90..362bfacb 100644 --- a/src/ext/content-scripts/entry-userscripts.js +++ b/src/ext/content-scripts/entry-userscripts.js @@ -25,19 +25,23 @@ function triageJS(userscript) { if (document.readyState !== "loading") { injectJS(userscript); } else { - document.addEventListener("DOMContentLoaded", () => { - injectJS(userscript); - }); + document.addEventListener( + "DOMContentLoaded", + () => injectJS(userscript), + { once: true }, + ); } } else if (runAt === "document-idle") { if (document.readyState === "complete") { injectJS(userscript); } else { - document.addEventListener("readystatechange", () => { + const handle = () => { if (document.readyState === "complete") { injectJS(userscript); + document.removeEventListener("readystatechange", handle); } - }); + }; + document.addEventListener("readystatechange", handle); } } } @@ -233,17 +237,23 @@ async function injection() { } function listeners() { - // listens for messages from background, popup, etc... - browser.runtime.onMessage.addListener((request) => { - const name = request.name; + /** listen for CSP violations */ + document.addEventListener("securitypolicyviolation", cspFallback, { + once: true, + }); + /** + * listens for messages from background, popup, etc... + * @type {import("webextension-polyfill").Runtime.OnMessageListener} + */ + const handleMessage = (message) => { + const name = message.name; if (name === "CONTEXT_RUN") { // from bg script when context-menu item is clicked // double check to ensure context-menu scripts only run in top windows if (window !== window.top) return; - // loop through context-menu scripts saved to data object and find match // if no match found, nothing will execute and error will log - const filename = request.menuItemId; + const filename = message.menuItemId; for (let i = 0; i < data.files.menu.length; i++) { const item = data.files.menu[i]; if (item.scriptObject.filename === filename) { @@ -254,9 +264,18 @@ function listeners() { } console.error(`Couldn't find ${filename} code!`); } + }; + /** Dynamically remove listeners to avoid memory leaks */ + if (document.visibilityState === "visible") { + browser.runtime.onMessage.addListener(handleMessage); + } + document.addEventListener("visibilitychange", () => { + if (document.hidden) { + browser.runtime.onMessage.removeListener(handleMessage); + } else { + browser.runtime.onMessage.addListener(handleMessage); + } }); - // listen for CSP violations - document.addEventListener("securitypolicyviolation", cspFallback); } async function initialize() { diff --git a/src/ext/extension-page/App.svelte b/src/ext/extension-page/App.svelte index 22487e8f..1c4ce4d3 100644 --- a/src/ext/extension-page/App.svelte +++ b/src/ext/extension-page/App.svelte @@ -19,6 +19,15 @@ } }); + /* Override theme mode */ + $: if ($settings["theme_mode"]) { + if ($settings["theme_mode"] === "auto") { + document.body.style.removeProperty("color-scheme"); + } else { + document.body.style.setProperty("color-scheme", $settings["theme_mode"]); + } + } + // disables default cmd+s (save) and cmd+f (find) behavior function preventKeyCommands(e) { if (e.metaKey && (e.code === "KeyS" || e.code === "KeyF")) { diff --git a/src/ext/shared/settings.js b/src/ext/shared/settings.js index 37d83e1d..6154d6bd 100644 --- a/src/ext/shared/settings.js +++ b/src/ext/shared/settings.js @@ -124,6 +124,14 @@ const settingsDefinition = /** @type {const} */ [ group: "general", nodeType: "Toggle", }, + { + name: "theme_mode", + type: "string", + values: ["auto", "dark", "light"], + default: "auto", + group: "general", + nodeType: "select", + }, { name: "global_active", type: "boolean", diff --git a/src/shared/variables.css b/src/shared/variables.css index 225c2ae6..f014eba2 100644 --- a/src/shared/variables.css +++ b/src/shared/variables.css @@ -24,80 +24,52 @@ --editor-matched-highlight: rgb(116 178 235 / 0.2); --editor-search-highlight: rgb(255 166 0 / 0.3); - /* Dark theme (default) */ - --color-bg-primary: #323639; - --color-bg-secondary: #2f3337; - --color-bg-theme: var(--color-black); - --color-script-highlighted: #364049; - --color-black: #1d2023; - --color-white: #fff; - --color-blue: #74b1eb; - --color-green: #60f36c; - --color-grey: rgb(255 255 255 / 0.15); - --color-red: #ff453a; - --color-yellow: #e4f360; - --text-color-primary: rgb(255 255 255); - --text-color-secondary: rgb(255 255 255 / 0.65); - --text-color-disabled: rgb(255 255 255 / 0.4); - --box-shadow: 0 0.5rem 1rem 0 rgb(0 0 0 / 0.25); - --button-color: var(--color-black); - --border-color: var(--color-black); + /* Color Theme */ + color-scheme: light dark; + --color-bg-primary: light-dark(#fafafa, #323639); + --color-bg-secondary: light-dark(#f1f1f1, #2f3337); + --color-bg-theme: light-dark(var(--color-white), var(--color-black)); + --color-script-highlighted: light-dark(#e2ebf3, #364049); + --color-black: light-dark(#333, #1d2023); + --color-white: light-dark(#fff, #fff); + --color-blue: light-dark(#317eff, #74b1eb); + --color-green: light-dark(#2bb239, #60f36c); + --color-grey: light-dark(rgb(51 51 51 / 0.15), rgb(255 255 255 / 0.15)); + --color-red: light-dark(#ff453a, #ff453a); + --color-yellow: light-dark(#b8722c, #e4f360); + --text-color-primary: light-dark(rgb(51 51 51), rgb(255 255 255)); + --text-color-secondary: light-dark( + rgb(51 51 51 / 0.65), + rgb(255 255 255 / 0.65) + ); + --text-color-disabled: light-dark( + rgb(51 51 51 / 0.35), + rgb(255 255 255 / 0.4) + ); + --box-shadow: 0 0.5rem 1rem 0 light-dark(rgb(0 0 0 / 0.15), rgb(0 0 0 / 0.25)); + --button-color: light-dark(var(--color-white), var(--color-black)); + --border-color: light-dark(var(--color-grey), var(--color-black)); - /* Editor dark theme colors */ - --editor-invisible: rgb(255 255 255 / 0.15); - --editor-number: #77e26a; - --editor-comment: rgb(255 255 255 / 0.35); - --editor-def: #efc371; - --editor-default: #cdcfd1; - --editor-keyword: #96c3ed; - --editor-atom: #59ebf5; - --editor-operator: #8c99a7; - --editor-property: #e86c8a; - --editor-string: #f5eea2; - --editor-string-2: #cdabff; - --editor-cursor: #e3e7eb; - --editor-matching-bracket-color: #fff; - --editor-matching-bracket-border: var(--editor-number); - --editor-blurred-background: rgb(50 54 57 / 0.65); -} - -/* Light theme */ -@media (prefers-color-scheme: light) { - :root { - color-scheme: light dark; - --color-bg-primary: #fafafa; - --color-bg-secondary: #f1f1f1; - --color-bg-theme: var(--color-white); - --color-script-highlighted: #e2ebf3; - --color-black: #333; - --color-white: #fff; - --color-blue: #317eff; - --color-green: #2bb239; - --color-grey: rgb(51 51 51 / 0.15); - --color-red: #ff453a; - --color-yellow: #b8722c; - --text-color-primary: rgb(51 51 51); - --text-color-secondary: rgb(51 51 51 / 0.65); - --text-color-disabled: rgb(51 51 51 / 0.35); - --box-shadow: 0 0.25rem 1rem 0 rgb(0 0 0 / 0.15); - --button-color: var(--color-white); - --border-color: var(--color-grey); - - /* Editor light theme colors */ - --editor-invisible: rgb(51 51 51 / 0.15); - --editor-number: var(--color-green); - --editor-comment: var(--text-color-disabled); - --editor-def: #b86d17; - --editor-default: #777; - --editor-keyword: #3a68ff; - --editor-atom: #00adad; - --editor-operator: #8c99a7; - --editor-property: #ff453a; - --editor-string: var(--editor-atom); - --editor-string-2: #a34fe6; - --editor-cursor: #7e7e7f; - --editor-matching-bracket-color: var(--text-color-primary); - --editor-matching-bracket-border: #ff00ea; - --editor-blurred-background: rgb(255 255 255 / 0.65); - } + /* Editor colors */ + --editor-invisible: light-dark(rgb(51 51 51 / 0.15), rgb(255 255 255 / 0.15)); + --editor-number: light-dark(var(--color-green), #77e26a); + --editor-comment: light-dark( + var(--text-color-disabled), + rgb(255 255 255 / 0.35) + ); + --editor-def: light-dark(#b86d17, #efc371); + --editor-default: light-dark(#777, #cdcfd1); + --editor-keyword: light-dark(#3a68ff, #96c3ed); + --editor-atom: light-dark(#00adad, #59ebf5); + --editor-operator: light-dark(#8c99a7, #8c99a7); + --editor-property: light-dark(#ff453a, #e86c8a); + --editor-string: light-dark(var(--editor-atom), #f5eea2); + --editor-string-2: light-dark(#a34fe6, #cdabff); + --editor-cursor: light-dark(#7e7e7f, #e3e7eb); + --editor-matching-bracket-color: light-dark(var(--text-color-primary), #fff); + --editor-matching-bracket-border: light-dark(#ff00ea, var(--editor-number)); + --editor-blurred-background: light-dark( + rgb(255 255 255 / 0.65), + rgb(50 54 57 / 0.65) + ); } diff --git a/xcode/App-Mac/ViewController.swift b/xcode/App-Mac/ViewController.swift index fc7598ee..e09d909c 100644 --- a/xcode/App-Mac/ViewController.swift +++ b/xcode/App-Mac/ViewController.swift @@ -36,7 +36,7 @@ class ViewController: NSViewController { 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, privacy: .public)") } else { logger?.error("\(#function, privacy: .public) - couldn't get safari extension state in containing app") } diff --git a/xcode/App-Shared/ViewController.swift b/xcode/App-Shared/ViewController.swift index db573177..83e0040d 100644 --- a/xcode/App-Shared/ViewController.swift +++ b/xcode/App-Shared/ViewController.swift @@ -149,7 +149,7 @@ class ViewController: PlatformViewController, WKNavigationDelegate, WKScriptMess self.extensionState = state.isEnabled ? .on : .off } catch { self.extensionState = .error - logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)") + logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)") } if #available(macOS 13, *) { useSettingsInsteadOfPreferences = true @@ -193,7 +193,7 @@ class ViewController: PlatformViewController, WKNavigationDelegate, WKScriptMess do { try await SFSafariApplication.showPreferencesForExtension(withIdentifier: extIdentifier) } catch { - logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)") + logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)") return (nil, error.localizedDescription) } break @@ -344,7 +344,7 @@ extension ViewController { } else { status = .error if let error = error { - logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)") + logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)") } else { logger?.error("\(#function, privacy: .public) - couldn't get safari extension state in containing app") } diff --git a/xcode/Ext-Safari/Functions.swift b/xcode/Ext-Safari/Functions.swift index 7163c9eb..220a380a 100644 --- a/xcode/Ext-Safari/Functions.swift +++ b/xcode/Ext-Safari/Functions.swift @@ -94,16 +94,20 @@ func isEncoded(_ str: String) -> Bool { return false } +// parser REs - force try b/c pattern is known to be valid regex +let re0 = try! NSRegularExpression(pattern: #"(?:(\/\/ ==UserScript==[ \t]*?\r?\n([\S\s]*?)\r?\n\/\/ ==\/UserScript==)([\S\s]*)|(\/\* ==UserStyle==[ \t]*?\r?\n([\S\s]*?)\r?\n==\/UserStyle== \*\/)([\S\s]*))"#, options: []) +// RE for meta line +let re1 = try! NSRegularExpression(pattern: #"^(?:[ \t]*(?:\/\/)?[ \t]*@)([\w-]+)[ \t]+([^\s]+[^\r\n\t\v\f]*)"#, options: []) +// this pattern checks for specific keys that won't have values +let re2 = try! NSRegularExpression(pattern: #"^(?:[ \t]*(?:\/\/)?[ \t]*@)(noframes)[ \t]*$"#, options: []) + // parser func parse(_ content: String) -> [String: Any]? { // returns structured data from content of file // will fail to parse if metablock or required @name key missing - let pattern = #"(?:(\/\/ ==UserScript==[ \t]*?\r?\n([\S\s]*?)\r?\n\/\/ ==\/UserScript==)([\S\s]*)|(\/\* ==UserStyle==[ \t]*?\r?\n([\S\s]*?)\r?\n==\/UserStyle== \*\/)([\S\s]*))"# - // force try b/c pattern is known to be valid regex - let regex = try! NSRegularExpression(pattern: pattern, options: []) let range = NSRange(location: 0, length: content.utf16.count) // return nil/fail if metablock missing - guard let match = regex.firstMatch(in: content, options: [], range: range) else { + guard let match = re0.firstMatch(in: content, options: [], range: range) else { logger?.debug("\(#function, privacy: .public) - Non matched content: \(content, privacy: .public)") return nil } @@ -138,17 +142,11 @@ func parse(_ content: String) -> [String: Any]? { let metaArray = content[metas].split(whereSeparator: \.isNewline) // loop through metadata lines and populate metadata dictionary for meta in metaArray { - let p = #"^(?:[ \t]*(?:\/\/)?[ \t]*@)([\w-]+)[ \t]+([^\s]+[^\r\n\t\v\f]*)"# - // this pattern checks for specific keys that won't have values - let p2 = #"^(?:[ \t]*(?:\/\/)?[ \t]*@)(noframes)[ \t]*$"# // the individual meta string, ie. // @name File Name let metaString = String(meta).trimmingCharacters(in: .whitespaces) - // force try b/c pattern is known to be valid regex - let re = try! NSRegularExpression(pattern: p, options: []) - let re2 = try! NSRegularExpression(pattern: p2, options: []) let range = NSRange(location: 0, length: metaString.utf16.count) // key lines not properly prefixed & without values will be skipped - if let m = re.firstMatch(in: metaString, options: [], range: range) { + if let m = re1.firstMatch(in: metaString, options: [], range: range) { // force unwrap key & value since matches regex above let key = metaString[Range(m.range(at: 1), in: metaString)!] let value = metaString[Range(m.range(at: 2), in: metaString)!] @@ -220,7 +218,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, privacy: .public)") return false } } @@ -742,7 +740,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, privacy: .public)") } } return true @@ -796,11 +794,11 @@ func getRequiredCode(_ filename: String, _ resources: [String], _ fileType: Stri try FileManager.default.removeItem(at: abandonFileUrl) 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, 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, privacy: .public)") } return true } @@ -877,7 +875,7 @@ func getRemoteFileContents(_ url: String) -> String? { } } if let error = error { - logger?.error("\(#function, privacy: .public) - task error: \(error.localizedDescription, privacy: .public) (\(url, privacy: .public))") + logger?.error("\(#function, privacy: .public) - task error: \(error, privacy: .public) (\(url, privacy: .public))") } semaphore.signal() } @@ -984,7 +982,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, privacy: .public)") return false } } @@ -1003,6 +1001,9 @@ func stringToRegex(_ stringPattern: String) -> NSRegularExpression? { return regex } +// RE for parse matchPattern +let re3 = try! NSRegularExpression(pattern: #"^(http:|https:|\*:)\/\/((?:\*\.)?(?:[a-z0-9-]+\.)+(?:[a-z0-9]+)|\*\.[a-z]+|\*|[a-z0-9]+)(\/[^\s]*)$"#, options: .caseInsensitive) + func match(_ url: String, _ matchPattern: String) -> Bool { guard let parts = jsLikeURL(url), @@ -1028,10 +1029,8 @@ func match(_ url: String, _ matchPattern: String) -> Bool { if (ptcl != "http:" && ptcl != "https:") { return false } - let partsPattern = #"^(http:|https:|\*:)\/\/((?:\*\.)?(?:[a-z0-9-]+\.)+(?:[a-z0-9]+)|\*\.[a-z]+|\*|[a-z0-9]+)(\/[^\s]*)$"# - 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 { + guard let parts = re3.firstMatch(in: matchPattern, options: [], range: range) else { logger?.error("\(#function, privacy: .public) - malformed regex match pattern - \(matchPattern, privacy: .public)") return false } @@ -1763,7 +1762,7 @@ 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, privacy: .public)") return false } } diff --git a/xcode/Shared/Preferences.swift b/xcode/Shared/Preferences.swift index d26e10ed..20f0b60e 100644 --- a/xcode/Shared/Preferences.swift +++ b/xcode/Shared/Preferences.swift @@ -60,7 +60,7 @@ private struct SecurityScopedBookmark { relativeTo: nil ) } catch { - logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)") + logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)") } return nil } @@ -74,7 +74,7 @@ private struct SecurityScopedBookmark { bookmarkDataIsStale: &isStale ) } catch { - logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)") + logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)") } return nil } @@ -202,7 +202,7 @@ private struct SecurityScopedBookmark { do { return try url.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil) } catch { - logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)") + logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)") } return nil } @@ -211,7 +211,7 @@ private struct SecurityScopedBookmark { do { return try URL(resolvingBookmarkData: data, bookmarkDataIsStale: &isStale) } catch { - logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)") + logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)") } return nil } @@ -237,7 +237,9 @@ private struct SecurityScopedBookmark { } var isStale = false guard let url = resolvBookmark(data, &isStale) else { // get security-scoped URL - SDefaults?.removeObject(forKey: key) // remove data that cannot be resolved + // Note: temporary mitigation measures for occasional failures on some iOS devices +// SDefaults?.removeObject(forKey: key) // remove data that cannot be resolved + logger?.warning("\(#function, privacy: .public) - faild resolv bookmark, return default") return defaultValue } if isStale, url.startAccessingSecurityScopedResource() { // renew URL bookmark