├── .assets
└── bundles.png
├── .gitignore
├── LICENSE.md
├── Phage Injector
├── Base.lproj
│ └── SafariExtensionViewController.xib
├── Info.plist
├── Phage_Injector.entitlements
├── SafariExtensionHandler.swift
├── SafariExtensionViewController.swift
├── ToolbarItemIcon.pdf
├── page-script.js
└── script.js
├── Phage.xcodeproj
├── project.pbxproj
└── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── Phage
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── icon_128x128.png
│ │ ├── icon_128x128@2x.png
│ │ ├── icon_16x16.png
│ │ ├── icon_16x16@2x.png
│ │ ├── icon_256x256.png
│ │ ├── icon_256x256@2x.png
│ │ ├── icon_32x32.png
│ │ ├── icon_32x32@2x.png
│ │ ├── icon_512x512.png
│ │ └── icon_512x512@2x.png
│ └── Contents.json
├── Base.lproj
│ └── Main.storyboard
├── ContentView.swift
├── Info.plist
├── Phage.entitlements
└── Preview Content
│ └── Preview Assets.xcassets
│ └── Contents.json
├── PhageCore
├── Info.plist
├── PhageData.swift
├── PhageDataBundle.swift
├── PhageDataFile.swift
└── PhageDependencies.swift
├── PhageCoreTests
├── Info.plist
└── PhageCoreTests.swift
├── PhageTests
├── Info.plist
└── PhageTests.swift
├── PhageUITests
├── Info.plist
└── PhageUITests.swift
└── README.md
/.assets/bundles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/.assets/bundles.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /ignore/
2 | /build/
3 | xcuserdata/
4 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | =====================
3 |
4 | Copyright © 2018 cpsdqs
5 |
6 | Permission is hereby granted, free of charge, to any person
7 | obtaining a copy of this software and associated documentation
8 | files (the “Software”), to deal in the Software without
9 | restriction, including without limitation the rights to use,
10 | copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the
12 | Software is furnished to do so, subject to the following
13 | conditions:
14 |
15 | The above copyright notice and this permission notice shall be
16 | included in all copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25 | OTHER DEALINGS IN THE SOFTWARE.
26 |
--------------------------------------------------------------------------------
/Phage Injector/Base.lproj/SafariExtensionViewController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/Phage Injector/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Phage Injector
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | 1
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSExtension
26 |
27 | NSExtensionPointIdentifier
28 | com.apple.Safari.extension
29 | NSExtensionPrincipalClass
30 | $(PRODUCT_MODULE_NAME).SafariExtensionHandler
31 | SFSafariContentScript
32 |
33 |
34 | Script
35 | script.js
36 |
37 |
38 | SFSafariToolbarItem
39 |
40 | Action
41 | Popover
42 | Identifier
43 | Button
44 | Image
45 | ToolbarItemIcon.pdf
46 | Label
47 | Phage
48 |
49 | SFSafariWebsiteAccess
50 |
51 | Level
52 | All
53 |
54 |
55 | NSHumanReadableCopyright
56 | Copyright © 2019 cpsdqs. All rights reserved.
57 | NSHumanReadableDescription
58 | Injects scripts and stylesheets.
59 | TeamIdentifierPrefix
60 | $(TeamIdentifierPrefix)
61 |
62 |
63 |
--------------------------------------------------------------------------------
/Phage Injector/Phage_Injector.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.application-groups
8 |
9 | $(TeamIdentifierPrefix)net.cloudwithlightning.phage
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Phage Injector/SafariExtensionHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SafariExtensionHandler.swift
3 | // Phage Extension
4 | //
5 | // Created by cpsdqs on 2019-06-12.
6 | // Copyright © 2019 cpsdqs. All rights reserved.
7 | //
8 |
9 | import SafariServices
10 | import PhageCore
11 | import Combine
12 |
13 | class SafariExtensionHandler: SFSafariExtensionHandler {
14 |
15 | let data = PhageData()
16 |
17 | override init() {
18 | super.init()
19 | }
20 |
21 | /// Runs the action once on every page.
22 | /// - Parameter action: the action to run on every page
23 | private func performOnAllPages(action: @escaping (SFSafariPage) -> Void) {
24 | SFSafariApplication.getAllWindows { windows in
25 | for window in windows {
26 | window.getAllTabs { tabs in
27 | for tab in tabs {
28 | tab.getActivePage { page in
29 | if let page = page {
30 | action(page)
31 | }
32 | }
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
39 | private func serializeBundle(name: String, matching url: URL) -> [String: Any]? {
40 | guard let bundle = data.bundles[name] else { return nil }
41 |
42 | var scripts: [[String: Any]] = []
43 | var styles: [[String: Any]] = []
44 |
45 | for (name, file) in bundle.files {
46 | switch file.contents() {
47 | case .some(.javascript(let section)):
48 | if section.matches(url: url) {
49 | var prelude = ""
50 | for dependency in file.dependencies() {
51 | let stringEscapedName = dependency
52 | .replacingOccurrences(of: "\\", with: "\\\\")
53 | .replacingOccurrences(of: "\"", with: "\\\"")
54 | .replacingOccurrences(of: "\n", with: "\\\n")
55 | if let url = URL(string: dependency) {
56 | if let data = data.dependencies.getDependencyContents(of: url) {
57 | let commentEscapedName = dependency.replacingOccurrences(of: "*/", with: "* /")
58 | prelude.append("\n/* Dependency: \(commentEscapedName) */\n")
59 | prelude.append(data)
60 | } else {
61 | prelude.append(";alert(\"[Phage]\n\nMissing dependency “\(stringEscapedName).” Use the Phage app to download it\");\n")
62 | }
63 | } else {
64 | prelude.append(";console.error(\"[Phage] invalid dependency \(stringEscapedName)\");\n")
65 | }
66 | }
67 |
68 | scripts.append([
69 | "name": name,
70 | "prelude": prelude,
71 | "contents": section.contents,
72 | "inPageContext": file.inPageContext
73 | ])
74 | }
75 | case .some(.stylesheets(let sections)):
76 | for (i, section) in sections.enumerated() {
77 | if section.matches(url: url) {
78 | styles.append([
79 | "id": [name, i],
80 | "contents": section.contents,
81 | ])
82 | }
83 | }
84 | case .none:
85 | break
86 | }
87 | }
88 |
89 | if scripts.isEmpty && styles.isEmpty {
90 | return nil
91 | }
92 |
93 | scripts.sort { (lhs, rhs) in
94 | let lhsName = lhs["name"] as! String
95 | let rhsName = rhs["name"] as! String
96 | return lhsName.lexicographicallyPrecedes(rhsName)
97 | }
98 |
99 | return [
100 | "id": name,
101 | "scripts": scripts,
102 | "styles": styles,
103 | ]
104 | }
105 |
106 | func serializeBundlesMatching(url: URL) -> [[String: Any]] {
107 | var bundles: [[String: Any]] = []
108 | for (name, bundle) in data.bundles {
109 | if bundle.disabled {
110 | continue
111 | }
112 | if let serialized = serializeBundle(name: name, matching: url) {
113 | bundles.append(serialized)
114 | }
115 | }
116 | return bundles
117 | }
118 |
119 | // MARK: Bundle update handling
120 | var version = 0
121 |
122 | func dispatchUpdateNotification() {
123 | version = version + 1
124 | NSLog("dispatching update notification")
125 | performOnAllPages { page in
126 | page.dispatchMessageToScript(withName: "updateAvailable", userInfo: [:])
127 | }
128 | }
129 |
130 | func sendCompleteUpdate(to page: SFSafariPage, url: URL, session: String) {
131 | page.dispatchMessageToScript(withName: "updateStyles", userInfo: [
132 | "sessionID": session,
133 | "updated": serializeBundlesMatching(url: url),
134 | "replace": true,
135 | ])
136 | }
137 |
138 | // MARK: SFSafariExtensionHandling
139 |
140 | override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) {
141 | switch messageName {
142 | case "initInjector":
143 | guard let urlString = userInfo?["url"] as? String,
144 | let sessionID = userInfo?["sessionID"] as? String else { return }
145 |
146 | guard let url = URL(string: urlString) else { return }
147 |
148 | let bundles = serializeBundlesMatching(url: url)
149 | // NSLog("received init for page at \(url), sending bundles (\(bundles.count))")
150 |
151 | page.dispatchMessageToScript(withName: "initInjector", userInfo: [
152 | "sessionID": sessionID,
153 | "bundles": bundles,
154 | "version": version,
155 | ])
156 | case "updateRequest":
157 | guard let urlString = userInfo?["url"] as? String,
158 | let sessionID = userInfo?["sessionID"] as? String else { return }
159 |
160 | guard let url = URL(string: urlString) else { return }
161 |
162 | NSLog("received update request for page at \(url)")
163 |
164 | sendCompleteUpdate(to: page, url: url, session: sessionID)
165 | default:
166 | NSLog("Script sent unknown message \(messageName)?")
167 | break
168 | }
169 | }
170 |
171 | override func toolbarItemClicked(in window: SFSafariWindow) {
172 | // This method will be called when your toolbar item is clicked.
173 | NSLog("The extension's toolbar item was clicked")
174 | }
175 |
176 | override func validateToolbarItem(in window: SFSafariWindow, validationHandler: @escaping ((Bool, String) -> Void)) {
177 | // This is called when Safari's state changed in some way that would require the extension's toolbar item to be validated again.
178 | validationHandler(true, "")
179 | }
180 |
181 | override func popoverViewController() -> SFSafariExtensionViewController {
182 | return SafariExtensionViewController.shared
183 | }
184 |
185 | }
186 |
--------------------------------------------------------------------------------
/Phage Injector/SafariExtensionViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SafariExtensionViewController.swift
3 | // Phage Injector
4 | //
5 | // Created by cpsdqs on 2019-06-17.
6 | // Copyright © 2019 cpsdqs. All rights reserved.
7 | //
8 |
9 | import SafariServices
10 | import SwiftUI
11 |
12 | class SafariExtensionViewController: SFSafariExtensionViewController {
13 |
14 | static let shared: SafariExtensionViewController = {
15 | let shared = SafariExtensionViewController()
16 | shared.preferredContentSize = NSSize(width:320, height:240)
17 | return shared
18 | }()
19 |
20 | override func viewDidLoad() {
21 | let hostingView = NSHostingView(rootView: PopoutView())
22 | view.addSubview(hostingView)
23 | hostingView.translatesAutoresizingMaskIntoConstraints = false
24 | hostingView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
25 | hostingView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
26 | hostingView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
27 | hostingView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
28 | }
29 |
30 | }
31 |
32 | struct PopoutView : View {
33 | var body: some View {
34 | VStack {
35 | Button(action: {
36 | SFSafariApplication.getActiveWindow { window in
37 | window?.getActiveTab { tab in
38 | tab?.getActivePage { page in
39 | page?.dispatchMessageToScript(withName: "forceUpdate", userInfo: [
40 | "action": "single"
41 | ])
42 | }
43 | }
44 | }
45 | }) {
46 | Text("Update stylesheets")
47 | }
48 | }.padding(8)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Phage Injector/ToolbarItemIcon.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage Injector/ToolbarItemIcon.pdf
--------------------------------------------------------------------------------
/Phage Injector/page-script.js:
--------------------------------------------------------------------------------
1 | // Phage Injector content script in page context
2 |
3 | {
4 | const EVT_NAME_IN = '__PHAGE_INJECTOR__EXT'
5 | const EVT_NAME_OUT = '__PHAGE_INJECTOR__PAGE'
6 |
7 | globalThis.addEventListener(EVT_NAME_IN, event => {
8 | if (!event.detail || typeof event.detail !== 'object') return
9 | if (event.detail.type === 'evalPageScript') {
10 | const fn = new Function(event.detail.source);
11 | fn.apply(window, [])
12 | }
13 | })
14 |
15 | globalThis.dispatchEvent(new globalThis.CustomEvent(EVT_NAME_OUT, {
16 | detail: {
17 | type: 'initPageInjector'
18 | }
19 | }))
20 | }
21 |
--------------------------------------------------------------------------------
/Phage Injector/script.js:
--------------------------------------------------------------------------------
1 | // Phage Injector content script in extension context
2 |
3 | ;(function() {
4 | // contents of this string must not contain single quotes or backslashes (see wrapScript):
5 | const phageConsoleStyle = 'color:white;background:black;padding:2px;border-radius:4px'
6 |
7 | if (globalThis.__phageInjectorSessionID) {
8 | console.warn(
9 | `%cPhage%c content script was injected twice? ignoring`,
10 | phageConsoleStyle,
11 | ''
12 | )
13 | return
14 | }
15 | const injectorSessionID = Math.random().toString(36)
16 | globalThis.__phageInjectorSessionID = injectorSessionID
17 |
18 | // TODO: handle push/popstate?
19 |
20 | let injectionTries = 0
21 |
22 | ;(function inject () {
23 | injectionTries++
24 | if (!window.document) {
25 | // try again later
26 | setTimeout(inject, 100 * 2 ** injectionTries)
27 | return
28 | }
29 |
30 | if (document.readyState !== 'complete' && location.protocol === 'about:') {
31 | // iframes that haven’t loaded yet will *navigate* from about:blank
32 | // to the target page so injection needs to be deferred to load time
33 | document.addEventListener('load', inject)
34 | return
35 | }
36 |
37 | safari.extension.dispatchMessage('initInjector', {
38 | url: window.location.href,
39 | sessionID: injectorSessionID,
40 | isTopLevel: window.top === window
41 | })
42 |
43 | let forceUpdateInterval = null
44 |
45 | function requestUpdate () {
46 | safari.extension.dispatchMessage('updateRequest', {
47 | url: window.location.href,
48 | sessionID: injectorSessionID,
49 | })
50 | }
51 |
52 | safari.self.addEventListener('message', event => {
53 | if (event.name === 'initInjector') {
54 | if (event.message.sessionID !== injectorSessionID) return
55 |
56 | for (const bundle of event.message.bundles) {
57 | injectScripts(bundle.id, bundle.scripts)
58 | injectStyles(bundle.id, bundle.styles)
59 | }
60 | } else if (event.name === 'forceUpdate') {
61 | if (event.message.action === 'single') {
62 | requestUpdate()
63 | } else if (event.message.action === 'begin' && forceUpdateInterval === null) {
64 | forceUpdateInterval = setInterval(requestUpdate, 1000)
65 | } else if (event.message.action === 'end' && forceUpdateInterval !== null) {
66 | clearInterval(forceUpdateInterval)
67 | }
68 | } else if (event.name === 'updateStyles') {
69 | if (event.message.sessionID !== injectorSessionID) return
70 |
71 | if (event.message.replace) {
72 | removeAllStyles()
73 | } else for (const bundleID of event.message.removed) {
74 | removeStyles(bundleID);
75 | }
76 | for (const bundle of event.message.updated) {
77 | injectStyles(bundle.id, bundle.styles);
78 | }
79 | }
80 | })
81 | })()
82 |
83 | function injectScripts (bundle, scripts) {
84 | let i = 0
85 | for (const script of scripts) {
86 | if (script.inPageContext) {
87 | initPageContext().then(ctx => {
88 | ctx.injectScript(wrapScript(script.name, script.prelude, script.contents));
89 | })
90 | } else if (script.asScriptTag) {
91 | const node = document.createElement('script')
92 | node.textContent = wrapScript(script.name, script.prelude, script.contents)
93 | node.id = `phagejs-${bundle}-${i}`
94 |
95 | if (document.head) document.head.appendChild(node)
96 | else document.addEventListener('load', () => document.head.appendChild(node))
97 | } else {
98 | const fn = new Function(wrapScript(script.name, script.prelude, script.contents))
99 | fn.apply(window, [])
100 | }
101 | i++
102 | }
103 | }
104 |
105 | let pageContextPromise = null
106 | function initPageContext () {
107 | const EVT_NAME_IN = '__PHAGE_INJECTOR__PAGE'
108 | const EVT_NAME_OUT = '__PHAGE_INJECTOR__EXT'
109 | if (!pageContextPromise) {
110 | pageContextPromise = new Promise(resolve => {
111 | const pageScript = document.createElement('script')
112 | pageScript.src = safari.extension.baseURI + 'page-script.js'
113 | if (document.head) document.head.appendChild(pageScript)
114 | else document.addEventListener('load', () => document.head.appendChild(node))
115 |
116 |
117 | const injectPageScript = source => {
118 | globalThis.dispatchEvent(new globalThis.CustomEvent(EVT_NAME_OUT, {
119 | detail: {
120 | type: 'evalPageScript',
121 | source
122 | }
123 | }))
124 | }
125 |
126 | globalThis.addEventListener(EVT_NAME_IN, event => {
127 | if (!event.detail || typeof event.detail !== 'object') return
128 | if (event.detail.type === 'initPageInjector') {
129 | resolve({
130 | injectScript: injectPageScript
131 | })
132 | }
133 | })
134 | })
135 | }
136 | return pageContextPromise
137 | }
138 |
139 | function wrapScript (name, prelude, contents) {
140 | name = name.replace(/\\/g, '\\\\').replace(/`/g, '\\`')
141 | return `/* Phage injected script */
142 | try {
143 | ${prelude}
144 | } catch (err) {
145 | console.error(
146 | \`%cPhage%c Script error for %c${name}%c in required code\`,
147 | '${phageConsoleStyle}',
148 | '',
149 | 'font-weight:bold',
150 | '',
151 | err
152 | )
153 | }
154 | try {
155 | ${contents}
156 | } catch (err) {
157 | console.error(
158 | \`%cPhage%c Script error for %c${name}%c\`,
159 | '${phageConsoleStyle}',
160 | '',
161 | 'font-weight:bold',
162 | '',
163 | err
164 | )
165 | }
166 | `
167 | }
168 |
169 | const injectedStyles = {}
170 | let injectIntoBody = false
171 |
172 | function injectStyles (bundle, styles) {
173 | // remove existing styles first
174 | removeStyles(bundle)
175 |
176 | const nodes = []
177 | let i = 0
178 | for (const style of styles) {
179 | const node = document.createElement('style')
180 | node.textContent = style.contents
181 | node.id = `phagecss-${bundle}-${i++}`
182 | nodes.push(node)
183 | }
184 |
185 | injectedStyles[bundle] = nodes
186 |
187 | if (injectIntoBody) {
188 | for (const node of nodes) document.body.appendChild(node)
189 | } else if (document.head) {
190 | for (const node of nodes) document.head.appendChild(node)
191 | } else {
192 | document.addEventListener('load', () => {
193 | // inject if still valid
194 | if (injectedStyles[bundle] === nodes) {
195 | for (const node of nodes) if (!node.parentNode) document.body.appendChild(node)
196 | }
197 | })
198 | }
199 | }
200 |
201 | window.addEventListener('DOMContentLoaded', () => {
202 | injectIntoBody = true
203 | for (const bundle in injectedStyles) {
204 | // move styles to the body so they have precedence
205 | for (const node of injectedStyles[bundle]) {
206 | if (node.parentNode) node.parentNode.removeChild(node)
207 | document.body.appendChild(node)
208 | }
209 | }
210 | })
211 |
212 | function removeStyles (bundle) {
213 | if (injectedStyles[bundle]) {
214 | for (const node of injectedStyles[bundle]) {
215 | if (node.parentNode) node.parentNode.removeChild(node)
216 | }
217 | }
218 | delete injectedStyles[bundle]
219 | }
220 |
221 | function removeAllStyles () {
222 | for (const bundle in injectedStyles) removeStyles(bundle)
223 | }
224 | })()
225 |
--------------------------------------------------------------------------------
/Phage.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 0F31D8F622B7DE26009D305A /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F31D8F522B7DE26009D305A /* Cocoa.framework */; };
11 | 0F31D8F922B7DE26009D305A /* SafariExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F31D8F822B7DE26009D305A /* SafariExtensionHandler.swift */; };
12 | 0F31D8FB22B7DE26009D305A /* SafariExtensionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F31D8FA22B7DE26009D305A /* SafariExtensionViewController.swift */; };
13 | 0F31D8FE22B7DE26009D305A /* SafariExtensionViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0F31D8FC22B7DE26009D305A /* SafariExtensionViewController.xib */; };
14 | 0F31D90122B7DE26009D305A /* script.js in Resources */ = {isa = PBXBuildFile; fileRef = 0F31D90022B7DE26009D305A /* script.js */; };
15 | 0F31D90322B7DE26009D305A /* ToolbarItemIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 0F31D90222B7DE26009D305A /* ToolbarItemIcon.pdf */; };
16 | 0F31D90722B7DE26009D305A /* Phage Injector.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0F31D8F322B7DE26009D305A /* Phage Injector.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
17 | 0FA5315E22B7D33000715AB8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FA5315D22B7D33000715AB8 /* AppDelegate.swift */; };
18 | 0FA5316022B7D33000715AB8 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FA5315F22B7D33000715AB8 /* ContentView.swift */; };
19 | 0FA5316222B7D33100715AB8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0FA5316122B7D33100715AB8 /* Assets.xcassets */; };
20 | 0FA5316522B7D33100715AB8 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0FA5316422B7D33100715AB8 /* Preview Assets.xcassets */; };
21 | 0FA5316822B7D33100715AB8 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0FA5316622B7D33100715AB8 /* Main.storyboard */; };
22 | 0FA5317422B7D33200715AB8 /* PhageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FA5317322B7D33200715AB8 /* PhageTests.swift */; };
23 | 0FA5317F22B7D33200715AB8 /* PhageUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FA5317E22B7D33200715AB8 /* PhageUITests.swift */; };
24 | 0FDFB3D522B7EA8200213CC7 /* PhageCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0FDFB3CC22B7EA8200213CC7 /* PhageCore.framework */; };
25 | 0FDFB3DC22B7EA8200213CC7 /* PhageCoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FDFB3DB22B7EA8200213CC7 /* PhageCoreTests.swift */; };
26 | 0FDFB3E122B7EA8200213CC7 /* PhageCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0FDFB3CC22B7EA8200213CC7 /* PhageCore.framework */; };
27 | 0FDFB3E222B7EA8200213CC7 /* PhageCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0FDFB3CC22B7EA8200213CC7 /* PhageCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
28 | 0FDFB3EA22B7EA9200213CC7 /* PhageCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0FDFB3CC22B7EA8200213CC7 /* PhageCore.framework */; };
29 | 0FDFB3EB22B7EA9200213CC7 /* PhageCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0FDFB3CC22B7EA8200213CC7 /* PhageCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
30 | 0FDFB3F022B7EAF700213CC7 /* PhageData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FDFB3EF22B7EAF700213CC7 /* PhageData.swift */; };
31 | 0FDFB3F422B8A10900213CC7 /* PhageDataBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FDFB3F322B8A10900213CC7 /* PhageDataBundle.swift */; };
32 | 0FDFB3F622B93C7E00213CC7 /* PhageDataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FDFB3F522B93C7E00213CC7 /* PhageDataFile.swift */; };
33 | 0FFB95BE22C2291D008D2FAE /* PhageDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FFB95BD22C2291D008D2FAE /* PhageDependencies.swift */; };
34 | 4E2B09BE27741C7E00B3E691 /* page-script.js in Resources */ = {isa = PBXBuildFile; fileRef = 4E2B09BD27741C7E00B3E691 /* page-script.js */; };
35 | /* End PBXBuildFile section */
36 |
37 | /* Begin PBXContainerItemProxy section */
38 | 0F31D90522B7DE26009D305A /* PBXContainerItemProxy */ = {
39 | isa = PBXContainerItemProxy;
40 | containerPortal = 0FA5315222B7D33000715AB8 /* Project object */;
41 | proxyType = 1;
42 | remoteGlobalIDString = 0F31D8F222B7DE26009D305A;
43 | remoteInfo = "Phage Injector";
44 | };
45 | 0FA5317022B7D33200715AB8 /* PBXContainerItemProxy */ = {
46 | isa = PBXContainerItemProxy;
47 | containerPortal = 0FA5315222B7D33000715AB8 /* Project object */;
48 | proxyType = 1;
49 | remoteGlobalIDString = 0FA5315922B7D33000715AB8;
50 | remoteInfo = Phage;
51 | };
52 | 0FA5317B22B7D33200715AB8 /* PBXContainerItemProxy */ = {
53 | isa = PBXContainerItemProxy;
54 | containerPortal = 0FA5315222B7D33000715AB8 /* Project object */;
55 | proxyType = 1;
56 | remoteGlobalIDString = 0FA5315922B7D33000715AB8;
57 | remoteInfo = Phage;
58 | };
59 | 0FDFB3D622B7EA8200213CC7 /* PBXContainerItemProxy */ = {
60 | isa = PBXContainerItemProxy;
61 | containerPortal = 0FA5315222B7D33000715AB8 /* Project object */;
62 | proxyType = 1;
63 | remoteGlobalIDString = 0FDFB3CB22B7EA8200213CC7;
64 | remoteInfo = PhageCore;
65 | };
66 | 0FDFB3D822B7EA8200213CC7 /* PBXContainerItemProxy */ = {
67 | isa = PBXContainerItemProxy;
68 | containerPortal = 0FA5315222B7D33000715AB8 /* Project object */;
69 | proxyType = 1;
70 | remoteGlobalIDString = 0FA5315922B7D33000715AB8;
71 | remoteInfo = Phage;
72 | };
73 | 0FDFB3DF22B7EA8200213CC7 /* PBXContainerItemProxy */ = {
74 | isa = PBXContainerItemProxy;
75 | containerPortal = 0FA5315222B7D33000715AB8 /* Project object */;
76 | proxyType = 1;
77 | remoteGlobalIDString = 0FDFB3CB22B7EA8200213CC7;
78 | remoteInfo = PhageCore;
79 | };
80 | 0FDFB3EC22B7EA9200213CC7 /* PBXContainerItemProxy */ = {
81 | isa = PBXContainerItemProxy;
82 | containerPortal = 0FA5315222B7D33000715AB8 /* Project object */;
83 | proxyType = 1;
84 | remoteGlobalIDString = 0FDFB3CB22B7EA8200213CC7;
85 | remoteInfo = PhageCore;
86 | };
87 | /* End PBXContainerItemProxy section */
88 |
89 | /* Begin PBXCopyFilesBuildPhase section */
90 | 0F31D90822B7DE26009D305A /* Embed App Extensions */ = {
91 | isa = PBXCopyFilesBuildPhase;
92 | buildActionMask = 2147483647;
93 | dstPath = "";
94 | dstSubfolderSpec = 13;
95 | files = (
96 | 0F31D90722B7DE26009D305A /* Phage Injector.appex in Embed App Extensions */,
97 | );
98 | name = "Embed App Extensions";
99 | runOnlyForDeploymentPostprocessing = 0;
100 | };
101 | 0FDFB3E622B7EA8200213CC7 /* Embed Frameworks */ = {
102 | isa = PBXCopyFilesBuildPhase;
103 | buildActionMask = 2147483647;
104 | dstPath = "";
105 | dstSubfolderSpec = 10;
106 | files = (
107 | 0FDFB3E222B7EA8200213CC7 /* PhageCore.framework in Embed Frameworks */,
108 | );
109 | name = "Embed Frameworks";
110 | runOnlyForDeploymentPostprocessing = 0;
111 | };
112 | 0FDFB3EE22B7EA9200213CC7 /* Embed Frameworks */ = {
113 | isa = PBXCopyFilesBuildPhase;
114 | buildActionMask = 2147483647;
115 | dstPath = "";
116 | dstSubfolderSpec = 10;
117 | files = (
118 | 0FDFB3EB22B7EA9200213CC7 /* PhageCore.framework in Embed Frameworks */,
119 | );
120 | name = "Embed Frameworks";
121 | runOnlyForDeploymentPostprocessing = 0;
122 | };
123 | /* End PBXCopyFilesBuildPhase section */
124 |
125 | /* Begin PBXFileReference section */
126 | 0F31D8F322B7DE26009D305A /* Phage Injector.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Phage Injector.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
127 | 0F31D8F522B7DE26009D305A /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
128 | 0F31D8F822B7DE26009D305A /* SafariExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariExtensionHandler.swift; sourceTree = ""; };
129 | 0F31D8FA22B7DE26009D305A /* SafariExtensionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariExtensionViewController.swift; sourceTree = ""; };
130 | 0F31D8FD22B7DE26009D305A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/SafariExtensionViewController.xib; sourceTree = ""; };
131 | 0F31D8FF22B7DE26009D305A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
132 | 0F31D90022B7DE26009D305A /* script.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = script.js; sourceTree = ""; };
133 | 0F31D90222B7DE26009D305A /* ToolbarItemIcon.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = ToolbarItemIcon.pdf; sourceTree = ""; };
134 | 0F31D90422B7DE26009D305A /* Phage_Injector.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Phage_Injector.entitlements; sourceTree = ""; };
135 | 0FA5315A22B7D33000715AB8 /* Phage.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Phage.app; sourceTree = BUILT_PRODUCTS_DIR; };
136 | 0FA5315D22B7D33000715AB8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
137 | 0FA5315F22B7D33000715AB8 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
138 | 0FA5316122B7D33100715AB8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
139 | 0FA5316422B7D33100715AB8 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
140 | 0FA5316722B7D33100715AB8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
141 | 0FA5316922B7D33200715AB8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
142 | 0FA5316A22B7D33200715AB8 /* Phage.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Phage.entitlements; sourceTree = ""; };
143 | 0FA5316F22B7D33200715AB8 /* PhageTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PhageTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
144 | 0FA5317322B7D33200715AB8 /* PhageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhageTests.swift; sourceTree = ""; };
145 | 0FA5317522B7D33200715AB8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
146 | 0FA5317A22B7D33200715AB8 /* PhageUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PhageUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
147 | 0FA5317E22B7D33200715AB8 /* PhageUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhageUITests.swift; sourceTree = ""; };
148 | 0FA5318022B7D33200715AB8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
149 | 0FDFB3CC22B7EA8200213CC7 /* PhageCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PhageCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
150 | 0FDFB3CF22B7EA8200213CC7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
151 | 0FDFB3D422B7EA8200213CC7 /* PhageCoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PhageCoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
152 | 0FDFB3DB22B7EA8200213CC7 /* PhageCoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhageCoreTests.swift; sourceTree = ""; };
153 | 0FDFB3DD22B7EA8200213CC7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
154 | 0FDFB3EF22B7EAF700213CC7 /* PhageData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhageData.swift; sourceTree = ""; };
155 | 0FDFB3F322B8A10900213CC7 /* PhageDataBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhageDataBundle.swift; sourceTree = ""; };
156 | 0FDFB3F522B93C7E00213CC7 /* PhageDataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhageDataFile.swift; sourceTree = ""; };
157 | 0FFB95BD22C2291D008D2FAE /* PhageDependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhageDependencies.swift; sourceTree = ""; };
158 | 4E2B09BD27741C7E00B3E691 /* page-script.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "page-script.js"; sourceTree = ""; };
159 | /* End PBXFileReference section */
160 |
161 | /* Begin PBXFrameworksBuildPhase section */
162 | 0F31D8F022B7DE26009D305A /* Frameworks */ = {
163 | isa = PBXFrameworksBuildPhase;
164 | buildActionMask = 2147483647;
165 | files = (
166 | 0F31D8F622B7DE26009D305A /* Cocoa.framework in Frameworks */,
167 | 0FDFB3EA22B7EA9200213CC7 /* PhageCore.framework in Frameworks */,
168 | );
169 | runOnlyForDeploymentPostprocessing = 0;
170 | };
171 | 0FA5315722B7D33000715AB8 /* Frameworks */ = {
172 | isa = PBXFrameworksBuildPhase;
173 | buildActionMask = 2147483647;
174 | files = (
175 | 0FDFB3E122B7EA8200213CC7 /* PhageCore.framework in Frameworks */,
176 | );
177 | runOnlyForDeploymentPostprocessing = 0;
178 | };
179 | 0FA5316C22B7D33200715AB8 /* Frameworks */ = {
180 | isa = PBXFrameworksBuildPhase;
181 | buildActionMask = 2147483647;
182 | files = (
183 | );
184 | runOnlyForDeploymentPostprocessing = 0;
185 | };
186 | 0FA5317722B7D33200715AB8 /* Frameworks */ = {
187 | isa = PBXFrameworksBuildPhase;
188 | buildActionMask = 2147483647;
189 | files = (
190 | );
191 | runOnlyForDeploymentPostprocessing = 0;
192 | };
193 | 0FDFB3C922B7EA8200213CC7 /* Frameworks */ = {
194 | isa = PBXFrameworksBuildPhase;
195 | buildActionMask = 2147483647;
196 | files = (
197 | );
198 | runOnlyForDeploymentPostprocessing = 0;
199 | };
200 | 0FDFB3D122B7EA8200213CC7 /* Frameworks */ = {
201 | isa = PBXFrameworksBuildPhase;
202 | buildActionMask = 2147483647;
203 | files = (
204 | 0FDFB3D522B7EA8200213CC7 /* PhageCore.framework in Frameworks */,
205 | );
206 | runOnlyForDeploymentPostprocessing = 0;
207 | };
208 | /* End PBXFrameworksBuildPhase section */
209 |
210 | /* Begin PBXGroup section */
211 | 0F31D8F422B7DE26009D305A /* Frameworks */ = {
212 | isa = PBXGroup;
213 | children = (
214 | 0F31D8F522B7DE26009D305A /* Cocoa.framework */,
215 | );
216 | name = Frameworks;
217 | sourceTree = "";
218 | };
219 | 0F31D8F722B7DE26009D305A /* Phage Injector */ = {
220 | isa = PBXGroup;
221 | children = (
222 | 0F31D8F822B7DE26009D305A /* SafariExtensionHandler.swift */,
223 | 0F31D8FA22B7DE26009D305A /* SafariExtensionViewController.swift */,
224 | 0F31D8FC22B7DE26009D305A /* SafariExtensionViewController.xib */,
225 | 0F31D8FF22B7DE26009D305A /* Info.plist */,
226 | 0F31D90022B7DE26009D305A /* script.js */,
227 | 0F31D90222B7DE26009D305A /* ToolbarItemIcon.pdf */,
228 | 0F31D90422B7DE26009D305A /* Phage_Injector.entitlements */,
229 | 4E2B09BD27741C7E00B3E691 /* page-script.js */,
230 | );
231 | path = "Phage Injector";
232 | sourceTree = "";
233 | };
234 | 0FA5315122B7D33000715AB8 = {
235 | isa = PBXGroup;
236 | children = (
237 | 0FA5315C22B7D33000715AB8 /* Phage */,
238 | 0F31D8F722B7DE26009D305A /* Phage Injector */,
239 | 0FDFB3CD22B7EA8200213CC7 /* PhageCore */,
240 | 0FA5317222B7D33200715AB8 /* PhageTests */,
241 | 0FA5317D22B7D33200715AB8 /* PhageUITests */,
242 | 0FDFB3DA22B7EA8200213CC7 /* PhageCoreTests */,
243 | 0F31D8F422B7DE26009D305A /* Frameworks */,
244 | 0FA5315B22B7D33000715AB8 /* Products */,
245 | );
246 | sourceTree = "";
247 | };
248 | 0FA5315B22B7D33000715AB8 /* Products */ = {
249 | isa = PBXGroup;
250 | children = (
251 | 0FA5315A22B7D33000715AB8 /* Phage.app */,
252 | 0FA5316F22B7D33200715AB8 /* PhageTests.xctest */,
253 | 0FA5317A22B7D33200715AB8 /* PhageUITests.xctest */,
254 | 0F31D8F322B7DE26009D305A /* Phage Injector.appex */,
255 | 0FDFB3CC22B7EA8200213CC7 /* PhageCore.framework */,
256 | 0FDFB3D422B7EA8200213CC7 /* PhageCoreTests.xctest */,
257 | );
258 | name = Products;
259 | sourceTree = "";
260 | };
261 | 0FA5315C22B7D33000715AB8 /* Phage */ = {
262 | isa = PBXGroup;
263 | children = (
264 | 0FA5315D22B7D33000715AB8 /* AppDelegate.swift */,
265 | 0FA5315F22B7D33000715AB8 /* ContentView.swift */,
266 | 0FA5316122B7D33100715AB8 /* Assets.xcassets */,
267 | 0FA5316622B7D33100715AB8 /* Main.storyboard */,
268 | 0FA5316922B7D33200715AB8 /* Info.plist */,
269 | 0FA5316A22B7D33200715AB8 /* Phage.entitlements */,
270 | 0FA5316322B7D33100715AB8 /* Preview Content */,
271 | );
272 | path = Phage;
273 | sourceTree = "";
274 | };
275 | 0FA5316322B7D33100715AB8 /* Preview Content */ = {
276 | isa = PBXGroup;
277 | children = (
278 | 0FA5316422B7D33100715AB8 /* Preview Assets.xcassets */,
279 | );
280 | path = "Preview Content";
281 | sourceTree = "";
282 | };
283 | 0FA5317222B7D33200715AB8 /* PhageTests */ = {
284 | isa = PBXGroup;
285 | children = (
286 | 0FA5317322B7D33200715AB8 /* PhageTests.swift */,
287 | 0FA5317522B7D33200715AB8 /* Info.plist */,
288 | );
289 | path = PhageTests;
290 | sourceTree = "";
291 | };
292 | 0FA5317D22B7D33200715AB8 /* PhageUITests */ = {
293 | isa = PBXGroup;
294 | children = (
295 | 0FA5317E22B7D33200715AB8 /* PhageUITests.swift */,
296 | 0FA5318022B7D33200715AB8 /* Info.plist */,
297 | );
298 | path = PhageUITests;
299 | sourceTree = "";
300 | };
301 | 0FDFB3CD22B7EA8200213CC7 /* PhageCore */ = {
302 | isa = PBXGroup;
303 | children = (
304 | 0FDFB3CF22B7EA8200213CC7 /* Info.plist */,
305 | 0FDFB3EF22B7EAF700213CC7 /* PhageData.swift */,
306 | 0FDFB3F322B8A10900213CC7 /* PhageDataBundle.swift */,
307 | 0FDFB3F522B93C7E00213CC7 /* PhageDataFile.swift */,
308 | 0FFB95BD22C2291D008D2FAE /* PhageDependencies.swift */,
309 | );
310 | path = PhageCore;
311 | sourceTree = "";
312 | };
313 | 0FDFB3DA22B7EA8200213CC7 /* PhageCoreTests */ = {
314 | isa = PBXGroup;
315 | children = (
316 | 0FDFB3DB22B7EA8200213CC7 /* PhageCoreTests.swift */,
317 | 0FDFB3DD22B7EA8200213CC7 /* Info.plist */,
318 | );
319 | path = PhageCoreTests;
320 | sourceTree = "";
321 | };
322 | /* End PBXGroup section */
323 |
324 | /* Begin PBXHeadersBuildPhase section */
325 | 0FDFB3C722B7EA8200213CC7 /* Headers */ = {
326 | isa = PBXHeadersBuildPhase;
327 | buildActionMask = 2147483647;
328 | files = (
329 | );
330 | runOnlyForDeploymentPostprocessing = 0;
331 | };
332 | /* End PBXHeadersBuildPhase section */
333 |
334 | /* Begin PBXNativeTarget section */
335 | 0F31D8F222B7DE26009D305A /* Phage Injector */ = {
336 | isa = PBXNativeTarget;
337 | buildConfigurationList = 0F31D90B22B7DE26009D305A /* Build configuration list for PBXNativeTarget "Phage Injector" */;
338 | buildPhases = (
339 | 0F31D8EF22B7DE26009D305A /* Sources */,
340 | 0F31D8F022B7DE26009D305A /* Frameworks */,
341 | 0F31D8F122B7DE26009D305A /* Resources */,
342 | 0FDFB3EE22B7EA9200213CC7 /* Embed Frameworks */,
343 | );
344 | buildRules = (
345 | );
346 | dependencies = (
347 | 0FDFB3ED22B7EA9200213CC7 /* PBXTargetDependency */,
348 | );
349 | name = "Phage Injector";
350 | productName = "Phage Injector";
351 | productReference = 0F31D8F322B7DE26009D305A /* Phage Injector.appex */;
352 | productType = "com.apple.product-type.app-extension";
353 | };
354 | 0FA5315922B7D33000715AB8 /* Phage */ = {
355 | isa = PBXNativeTarget;
356 | buildConfigurationList = 0FA5318322B7D33200715AB8 /* Build configuration list for PBXNativeTarget "Phage" */;
357 | buildPhases = (
358 | 0FA5315622B7D33000715AB8 /* Sources */,
359 | 0FA5315722B7D33000715AB8 /* Frameworks */,
360 | 0FA5315822B7D33000715AB8 /* Resources */,
361 | 0F31D90822B7DE26009D305A /* Embed App Extensions */,
362 | 0FDFB3E622B7EA8200213CC7 /* Embed Frameworks */,
363 | );
364 | buildRules = (
365 | );
366 | dependencies = (
367 | 0F31D90622B7DE26009D305A /* PBXTargetDependency */,
368 | 0FDFB3E022B7EA8200213CC7 /* PBXTargetDependency */,
369 | );
370 | name = Phage;
371 | productName = Phage;
372 | productReference = 0FA5315A22B7D33000715AB8 /* Phage.app */;
373 | productType = "com.apple.product-type.application";
374 | };
375 | 0FA5316E22B7D33200715AB8 /* PhageTests */ = {
376 | isa = PBXNativeTarget;
377 | buildConfigurationList = 0FA5318622B7D33200715AB8 /* Build configuration list for PBXNativeTarget "PhageTests" */;
378 | buildPhases = (
379 | 0FA5316B22B7D33200715AB8 /* Sources */,
380 | 0FA5316C22B7D33200715AB8 /* Frameworks */,
381 | 0FA5316D22B7D33200715AB8 /* Resources */,
382 | );
383 | buildRules = (
384 | );
385 | dependencies = (
386 | 0FA5317122B7D33200715AB8 /* PBXTargetDependency */,
387 | );
388 | name = PhageTests;
389 | productName = PhageTests;
390 | productReference = 0FA5316F22B7D33200715AB8 /* PhageTests.xctest */;
391 | productType = "com.apple.product-type.bundle.unit-test";
392 | };
393 | 0FA5317922B7D33200715AB8 /* PhageUITests */ = {
394 | isa = PBXNativeTarget;
395 | buildConfigurationList = 0FA5318922B7D33200715AB8 /* Build configuration list for PBXNativeTarget "PhageUITests" */;
396 | buildPhases = (
397 | 0FA5317622B7D33200715AB8 /* Sources */,
398 | 0FA5317722B7D33200715AB8 /* Frameworks */,
399 | 0FA5317822B7D33200715AB8 /* Resources */,
400 | );
401 | buildRules = (
402 | );
403 | dependencies = (
404 | 0FA5317C22B7D33200715AB8 /* PBXTargetDependency */,
405 | );
406 | name = PhageUITests;
407 | productName = PhageUITests;
408 | productReference = 0FA5317A22B7D33200715AB8 /* PhageUITests.xctest */;
409 | productType = "com.apple.product-type.bundle.ui-testing";
410 | };
411 | 0FDFB3CB22B7EA8200213CC7 /* PhageCore */ = {
412 | isa = PBXNativeTarget;
413 | buildConfigurationList = 0FDFB3E322B7EA8200213CC7 /* Build configuration list for PBXNativeTarget "PhageCore" */;
414 | buildPhases = (
415 | 0FDFB3C722B7EA8200213CC7 /* Headers */,
416 | 0FDFB3C822B7EA8200213CC7 /* Sources */,
417 | 0FDFB3C922B7EA8200213CC7 /* Frameworks */,
418 | 0FDFB3CA22B7EA8200213CC7 /* Resources */,
419 | );
420 | buildRules = (
421 | );
422 | dependencies = (
423 | );
424 | name = PhageCore;
425 | productName = PhageCore;
426 | productReference = 0FDFB3CC22B7EA8200213CC7 /* PhageCore.framework */;
427 | productType = "com.apple.product-type.framework";
428 | };
429 | 0FDFB3D322B7EA8200213CC7 /* PhageCoreTests */ = {
430 | isa = PBXNativeTarget;
431 | buildConfigurationList = 0FDFB3E722B7EA8200213CC7 /* Build configuration list for PBXNativeTarget "PhageCoreTests" */;
432 | buildPhases = (
433 | 0FDFB3D022B7EA8200213CC7 /* Sources */,
434 | 0FDFB3D122B7EA8200213CC7 /* Frameworks */,
435 | 0FDFB3D222B7EA8200213CC7 /* Resources */,
436 | );
437 | buildRules = (
438 | );
439 | dependencies = (
440 | 0FDFB3D722B7EA8200213CC7 /* PBXTargetDependency */,
441 | 0FDFB3D922B7EA8200213CC7 /* PBXTargetDependency */,
442 | );
443 | name = PhageCoreTests;
444 | productName = PhageCoreTests;
445 | productReference = 0FDFB3D422B7EA8200213CC7 /* PhageCoreTests.xctest */;
446 | productType = "com.apple.product-type.bundle.unit-test";
447 | };
448 | /* End PBXNativeTarget section */
449 |
450 | /* Begin PBXProject section */
451 | 0FA5315222B7D33000715AB8 /* Project object */ = {
452 | isa = PBXProject;
453 | attributes = {
454 | LastSwiftUpdateCheck = 1100;
455 | LastUpgradeCheck = 1100;
456 | ORGANIZATIONNAME = cpsdqs;
457 | TargetAttributes = {
458 | 0F31D8F222B7DE26009D305A = {
459 | CreatedOnToolsVersion = 11.0;
460 | };
461 | 0FA5315922B7D33000715AB8 = {
462 | CreatedOnToolsVersion = 11.0;
463 | };
464 | 0FA5316E22B7D33200715AB8 = {
465 | CreatedOnToolsVersion = 11.0;
466 | TestTargetID = 0FA5315922B7D33000715AB8;
467 | };
468 | 0FA5317922B7D33200715AB8 = {
469 | CreatedOnToolsVersion = 11.0;
470 | TestTargetID = 0FA5315922B7D33000715AB8;
471 | };
472 | 0FDFB3CB22B7EA8200213CC7 = {
473 | CreatedOnToolsVersion = 11.0;
474 | LastSwiftMigration = 1100;
475 | };
476 | 0FDFB3D322B7EA8200213CC7 = {
477 | CreatedOnToolsVersion = 11.0;
478 | };
479 | };
480 | };
481 | buildConfigurationList = 0FA5315522B7D33000715AB8 /* Build configuration list for PBXProject "Phage" */;
482 | compatibilityVersion = "Xcode 9.3";
483 | developmentRegion = en;
484 | hasScannedForEncodings = 0;
485 | knownRegions = (
486 | en,
487 | Base,
488 | );
489 | mainGroup = 0FA5315122B7D33000715AB8;
490 | productRefGroup = 0FA5315B22B7D33000715AB8 /* Products */;
491 | projectDirPath = "";
492 | projectRoot = "";
493 | targets = (
494 | 0FA5315922B7D33000715AB8 /* Phage */,
495 | 0F31D8F222B7DE26009D305A /* Phage Injector */,
496 | 0FA5316E22B7D33200715AB8 /* PhageTests */,
497 | 0FA5317922B7D33200715AB8 /* PhageUITests */,
498 | 0FDFB3CB22B7EA8200213CC7 /* PhageCore */,
499 | 0FDFB3D322B7EA8200213CC7 /* PhageCoreTests */,
500 | );
501 | };
502 | /* End PBXProject section */
503 |
504 | /* Begin PBXResourcesBuildPhase section */
505 | 0F31D8F122B7DE26009D305A /* Resources */ = {
506 | isa = PBXResourcesBuildPhase;
507 | buildActionMask = 2147483647;
508 | files = (
509 | 4E2B09BE27741C7E00B3E691 /* page-script.js in Resources */,
510 | 0F31D90322B7DE26009D305A /* ToolbarItemIcon.pdf in Resources */,
511 | 0F31D8FE22B7DE26009D305A /* SafariExtensionViewController.xib in Resources */,
512 | 0F31D90122B7DE26009D305A /* script.js in Resources */,
513 | );
514 | runOnlyForDeploymentPostprocessing = 0;
515 | };
516 | 0FA5315822B7D33000715AB8 /* Resources */ = {
517 | isa = PBXResourcesBuildPhase;
518 | buildActionMask = 2147483647;
519 | files = (
520 | 0FA5316822B7D33100715AB8 /* Main.storyboard in Resources */,
521 | 0FA5316522B7D33100715AB8 /* Preview Assets.xcassets in Resources */,
522 | 0FA5316222B7D33100715AB8 /* Assets.xcassets in Resources */,
523 | );
524 | runOnlyForDeploymentPostprocessing = 0;
525 | };
526 | 0FA5316D22B7D33200715AB8 /* Resources */ = {
527 | isa = PBXResourcesBuildPhase;
528 | buildActionMask = 2147483647;
529 | files = (
530 | );
531 | runOnlyForDeploymentPostprocessing = 0;
532 | };
533 | 0FA5317822B7D33200715AB8 /* Resources */ = {
534 | isa = PBXResourcesBuildPhase;
535 | buildActionMask = 2147483647;
536 | files = (
537 | );
538 | runOnlyForDeploymentPostprocessing = 0;
539 | };
540 | 0FDFB3CA22B7EA8200213CC7 /* Resources */ = {
541 | isa = PBXResourcesBuildPhase;
542 | buildActionMask = 2147483647;
543 | files = (
544 | );
545 | runOnlyForDeploymentPostprocessing = 0;
546 | };
547 | 0FDFB3D222B7EA8200213CC7 /* Resources */ = {
548 | isa = PBXResourcesBuildPhase;
549 | buildActionMask = 2147483647;
550 | files = (
551 | );
552 | runOnlyForDeploymentPostprocessing = 0;
553 | };
554 | /* End PBXResourcesBuildPhase section */
555 |
556 | /* Begin PBXSourcesBuildPhase section */
557 | 0F31D8EF22B7DE26009D305A /* Sources */ = {
558 | isa = PBXSourcesBuildPhase;
559 | buildActionMask = 2147483647;
560 | files = (
561 | 0F31D8FB22B7DE26009D305A /* SafariExtensionViewController.swift in Sources */,
562 | 0F31D8F922B7DE26009D305A /* SafariExtensionHandler.swift in Sources */,
563 | );
564 | runOnlyForDeploymentPostprocessing = 0;
565 | };
566 | 0FA5315622B7D33000715AB8 /* Sources */ = {
567 | isa = PBXSourcesBuildPhase;
568 | buildActionMask = 2147483647;
569 | files = (
570 | 0FA5316022B7D33000715AB8 /* ContentView.swift in Sources */,
571 | 0FA5315E22B7D33000715AB8 /* AppDelegate.swift in Sources */,
572 | );
573 | runOnlyForDeploymentPostprocessing = 0;
574 | };
575 | 0FA5316B22B7D33200715AB8 /* Sources */ = {
576 | isa = PBXSourcesBuildPhase;
577 | buildActionMask = 2147483647;
578 | files = (
579 | 0FA5317422B7D33200715AB8 /* PhageTests.swift in Sources */,
580 | );
581 | runOnlyForDeploymentPostprocessing = 0;
582 | };
583 | 0FA5317622B7D33200715AB8 /* Sources */ = {
584 | isa = PBXSourcesBuildPhase;
585 | buildActionMask = 2147483647;
586 | files = (
587 | 0FA5317F22B7D33200715AB8 /* PhageUITests.swift in Sources */,
588 | );
589 | runOnlyForDeploymentPostprocessing = 0;
590 | };
591 | 0FDFB3C822B7EA8200213CC7 /* Sources */ = {
592 | isa = PBXSourcesBuildPhase;
593 | buildActionMask = 2147483647;
594 | files = (
595 | 0FDFB3F022B7EAF700213CC7 /* PhageData.swift in Sources */,
596 | 0FDFB3F622B93C7E00213CC7 /* PhageDataFile.swift in Sources */,
597 | 0FFB95BE22C2291D008D2FAE /* PhageDependencies.swift in Sources */,
598 | 0FDFB3F422B8A10900213CC7 /* PhageDataBundle.swift in Sources */,
599 | );
600 | runOnlyForDeploymentPostprocessing = 0;
601 | };
602 | 0FDFB3D022B7EA8200213CC7 /* Sources */ = {
603 | isa = PBXSourcesBuildPhase;
604 | buildActionMask = 2147483647;
605 | files = (
606 | 0FDFB3DC22B7EA8200213CC7 /* PhageCoreTests.swift in Sources */,
607 | );
608 | runOnlyForDeploymentPostprocessing = 0;
609 | };
610 | /* End PBXSourcesBuildPhase section */
611 |
612 | /* Begin PBXTargetDependency section */
613 | 0F31D90622B7DE26009D305A /* PBXTargetDependency */ = {
614 | isa = PBXTargetDependency;
615 | target = 0F31D8F222B7DE26009D305A /* Phage Injector */;
616 | targetProxy = 0F31D90522B7DE26009D305A /* PBXContainerItemProxy */;
617 | };
618 | 0FA5317122B7D33200715AB8 /* PBXTargetDependency */ = {
619 | isa = PBXTargetDependency;
620 | target = 0FA5315922B7D33000715AB8 /* Phage */;
621 | targetProxy = 0FA5317022B7D33200715AB8 /* PBXContainerItemProxy */;
622 | };
623 | 0FA5317C22B7D33200715AB8 /* PBXTargetDependency */ = {
624 | isa = PBXTargetDependency;
625 | target = 0FA5315922B7D33000715AB8 /* Phage */;
626 | targetProxy = 0FA5317B22B7D33200715AB8 /* PBXContainerItemProxy */;
627 | };
628 | 0FDFB3D722B7EA8200213CC7 /* PBXTargetDependency */ = {
629 | isa = PBXTargetDependency;
630 | target = 0FDFB3CB22B7EA8200213CC7 /* PhageCore */;
631 | targetProxy = 0FDFB3D622B7EA8200213CC7 /* PBXContainerItemProxy */;
632 | };
633 | 0FDFB3D922B7EA8200213CC7 /* PBXTargetDependency */ = {
634 | isa = PBXTargetDependency;
635 | target = 0FA5315922B7D33000715AB8 /* Phage */;
636 | targetProxy = 0FDFB3D822B7EA8200213CC7 /* PBXContainerItemProxy */;
637 | };
638 | 0FDFB3E022B7EA8200213CC7 /* PBXTargetDependency */ = {
639 | isa = PBXTargetDependency;
640 | target = 0FDFB3CB22B7EA8200213CC7 /* PhageCore */;
641 | targetProxy = 0FDFB3DF22B7EA8200213CC7 /* PBXContainerItemProxy */;
642 | };
643 | 0FDFB3ED22B7EA9200213CC7 /* PBXTargetDependency */ = {
644 | isa = PBXTargetDependency;
645 | target = 0FDFB3CB22B7EA8200213CC7 /* PhageCore */;
646 | targetProxy = 0FDFB3EC22B7EA9200213CC7 /* PBXContainerItemProxy */;
647 | };
648 | /* End PBXTargetDependency section */
649 |
650 | /* Begin PBXVariantGroup section */
651 | 0F31D8FC22B7DE26009D305A /* SafariExtensionViewController.xib */ = {
652 | isa = PBXVariantGroup;
653 | children = (
654 | 0F31D8FD22B7DE26009D305A /* Base */,
655 | );
656 | name = SafariExtensionViewController.xib;
657 | sourceTree = "";
658 | };
659 | 0FA5316622B7D33100715AB8 /* Main.storyboard */ = {
660 | isa = PBXVariantGroup;
661 | children = (
662 | 0FA5316722B7D33100715AB8 /* Base */,
663 | );
664 | name = Main.storyboard;
665 | sourceTree = "";
666 | };
667 | /* End PBXVariantGroup section */
668 |
669 | /* Begin XCBuildConfiguration section */
670 | 0F31D90922B7DE26009D305A /* Debug */ = {
671 | isa = XCBuildConfiguration;
672 | buildSettings = {
673 | CODE_SIGN_ENTITLEMENTS = "Phage Injector/Phage_Injector.entitlements";
674 | CODE_SIGN_STYLE = Automatic;
675 | DEVELOPMENT_TEAM = S2DETNM9XB;
676 | ENABLE_HARDENED_RUNTIME = YES;
677 | INFOPLIST_FILE = "Phage Injector/Info.plist";
678 | LD_RUNPATH_SEARCH_PATHS = (
679 | "$(inherited)",
680 | "@executable_path/../Frameworks",
681 | "@executable_path/../../../../Frameworks",
682 | );
683 | PRODUCT_BUNDLE_IDENTIFIER = "net.cloudwithlightning.Phage.Phage-Injector";
684 | PRODUCT_NAME = "$(TARGET_NAME)";
685 | SKIP_INSTALL = YES;
686 | SWIFT_VERSION = 5.0;
687 | };
688 | name = Debug;
689 | };
690 | 0F31D90A22B7DE26009D305A /* Release */ = {
691 | isa = XCBuildConfiguration;
692 | buildSettings = {
693 | CODE_SIGN_ENTITLEMENTS = "Phage Injector/Phage_Injector.entitlements";
694 | CODE_SIGN_STYLE = Automatic;
695 | DEVELOPMENT_TEAM = S2DETNM9XB;
696 | ENABLE_HARDENED_RUNTIME = YES;
697 | INFOPLIST_FILE = "Phage Injector/Info.plist";
698 | LD_RUNPATH_SEARCH_PATHS = (
699 | "$(inherited)",
700 | "@executable_path/../Frameworks",
701 | "@executable_path/../../../../Frameworks",
702 | );
703 | PRODUCT_BUNDLE_IDENTIFIER = "net.cloudwithlightning.Phage.Phage-Injector";
704 | PRODUCT_NAME = "$(TARGET_NAME)";
705 | SKIP_INSTALL = YES;
706 | SWIFT_VERSION = 5.0;
707 | };
708 | name = Release;
709 | };
710 | 0FA5318122B7D33200715AB8 /* Debug */ = {
711 | isa = XCBuildConfiguration;
712 | buildSettings = {
713 | ALWAYS_SEARCH_USER_PATHS = NO;
714 | CLANG_ANALYZER_NONNULL = YES;
715 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
716 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
717 | CLANG_CXX_LIBRARY = "libc++";
718 | CLANG_ENABLE_MODULES = YES;
719 | CLANG_ENABLE_OBJC_ARC = YES;
720 | CLANG_ENABLE_OBJC_WEAK = YES;
721 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
722 | CLANG_WARN_BOOL_CONVERSION = YES;
723 | CLANG_WARN_COMMA = YES;
724 | CLANG_WARN_CONSTANT_CONVERSION = YES;
725 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
726 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
727 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
728 | CLANG_WARN_EMPTY_BODY = YES;
729 | CLANG_WARN_ENUM_CONVERSION = YES;
730 | CLANG_WARN_INFINITE_RECURSION = YES;
731 | CLANG_WARN_INT_CONVERSION = YES;
732 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
733 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
734 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
735 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
736 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
737 | CLANG_WARN_STRICT_PROTOTYPES = YES;
738 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
739 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
740 | CLANG_WARN_UNREACHABLE_CODE = YES;
741 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
742 | COPY_PHASE_STRIP = NO;
743 | DEBUG_INFORMATION_FORMAT = dwarf;
744 | ENABLE_STRICT_OBJC_MSGSEND = YES;
745 | ENABLE_TESTABILITY = YES;
746 | GCC_C_LANGUAGE_STANDARD = gnu11;
747 | GCC_DYNAMIC_NO_PIC = NO;
748 | GCC_NO_COMMON_BLOCKS = YES;
749 | GCC_OPTIMIZATION_LEVEL = 0;
750 | GCC_PREPROCESSOR_DEFINITIONS = (
751 | "DEBUG=1",
752 | "$(inherited)",
753 | );
754 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
755 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
756 | GCC_WARN_UNDECLARED_SELECTOR = YES;
757 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
758 | GCC_WARN_UNUSED_FUNCTION = YES;
759 | GCC_WARN_UNUSED_VARIABLE = YES;
760 | MACOSX_DEPLOYMENT_TARGET = 10.15;
761 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
762 | MTL_FAST_MATH = YES;
763 | ONLY_ACTIVE_ARCH = YES;
764 | SDKROOT = macosx;
765 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
766 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
767 | };
768 | name = Debug;
769 | };
770 | 0FA5318222B7D33200715AB8 /* Release */ = {
771 | isa = XCBuildConfiguration;
772 | buildSettings = {
773 | ALWAYS_SEARCH_USER_PATHS = NO;
774 | CLANG_ANALYZER_NONNULL = YES;
775 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
776 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
777 | CLANG_CXX_LIBRARY = "libc++";
778 | CLANG_ENABLE_MODULES = YES;
779 | CLANG_ENABLE_OBJC_ARC = YES;
780 | CLANG_ENABLE_OBJC_WEAK = YES;
781 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
782 | CLANG_WARN_BOOL_CONVERSION = YES;
783 | CLANG_WARN_COMMA = YES;
784 | CLANG_WARN_CONSTANT_CONVERSION = YES;
785 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
786 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
787 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
788 | CLANG_WARN_EMPTY_BODY = YES;
789 | CLANG_WARN_ENUM_CONVERSION = YES;
790 | CLANG_WARN_INFINITE_RECURSION = YES;
791 | CLANG_WARN_INT_CONVERSION = YES;
792 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
793 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
794 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
795 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
796 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
797 | CLANG_WARN_STRICT_PROTOTYPES = YES;
798 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
799 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
800 | CLANG_WARN_UNREACHABLE_CODE = YES;
801 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
802 | COPY_PHASE_STRIP = NO;
803 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
804 | ENABLE_NS_ASSERTIONS = NO;
805 | ENABLE_STRICT_OBJC_MSGSEND = YES;
806 | GCC_C_LANGUAGE_STANDARD = gnu11;
807 | GCC_NO_COMMON_BLOCKS = YES;
808 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
809 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
810 | GCC_WARN_UNDECLARED_SELECTOR = YES;
811 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
812 | GCC_WARN_UNUSED_FUNCTION = YES;
813 | GCC_WARN_UNUSED_VARIABLE = YES;
814 | MACOSX_DEPLOYMENT_TARGET = 10.15;
815 | MTL_ENABLE_DEBUG_INFO = NO;
816 | MTL_FAST_MATH = YES;
817 | SDKROOT = macosx;
818 | SWIFT_COMPILATION_MODE = wholemodule;
819 | SWIFT_OPTIMIZATION_LEVEL = "-O";
820 | };
821 | name = Release;
822 | };
823 | 0FA5318422B7D33200715AB8 /* Debug */ = {
824 | isa = XCBuildConfiguration;
825 | buildSettings = {
826 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
827 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
828 | CODE_SIGN_ENTITLEMENTS = Phage/Phage.entitlements;
829 | CODE_SIGN_STYLE = Automatic;
830 | COMBINE_HIDPI_IMAGES = YES;
831 | DEVELOPMENT_ASSET_PATHS = "Phage/Preview\\ Content";
832 | DEVELOPMENT_TEAM = S2DETNM9XB;
833 | ENABLE_HARDENED_RUNTIME = YES;
834 | ENABLE_PREVIEWS = YES;
835 | INFOPLIST_FILE = Phage/Info.plist;
836 | LD_RUNPATH_SEARCH_PATHS = (
837 | "$(inherited)",
838 | "@executable_path/../Frameworks",
839 | );
840 | PRODUCT_BUNDLE_IDENTIFIER = net.cloudwithlightning.Phage;
841 | PRODUCT_NAME = "$(TARGET_NAME)";
842 | SWIFT_VERSION = 5.0;
843 | };
844 | name = Debug;
845 | };
846 | 0FA5318522B7D33200715AB8 /* Release */ = {
847 | isa = XCBuildConfiguration;
848 | buildSettings = {
849 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
850 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
851 | CODE_SIGN_ENTITLEMENTS = Phage/Phage.entitlements;
852 | CODE_SIGN_STYLE = Automatic;
853 | COMBINE_HIDPI_IMAGES = YES;
854 | DEVELOPMENT_ASSET_PATHS = "Phage/Preview\\ Content";
855 | DEVELOPMENT_TEAM = S2DETNM9XB;
856 | ENABLE_HARDENED_RUNTIME = YES;
857 | ENABLE_PREVIEWS = YES;
858 | INFOPLIST_FILE = Phage/Info.plist;
859 | LD_RUNPATH_SEARCH_PATHS = (
860 | "$(inherited)",
861 | "@executable_path/../Frameworks",
862 | );
863 | PRODUCT_BUNDLE_IDENTIFIER = net.cloudwithlightning.Phage;
864 | PRODUCT_NAME = "$(TARGET_NAME)";
865 | SWIFT_VERSION = 5.0;
866 | };
867 | name = Release;
868 | };
869 | 0FA5318722B7D33200715AB8 /* Debug */ = {
870 | isa = XCBuildConfiguration;
871 | buildSettings = {
872 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
873 | BUNDLE_LOADER = "$(TEST_HOST)";
874 | CODE_SIGN_STYLE = Automatic;
875 | COMBINE_HIDPI_IMAGES = YES;
876 | DEVELOPMENT_TEAM = S2DETNM9XB;
877 | INFOPLIST_FILE = PhageTests/Info.plist;
878 | LD_RUNPATH_SEARCH_PATHS = (
879 | "$(inherited)",
880 | "@executable_path/../Frameworks",
881 | "@loader_path/../Frameworks",
882 | );
883 | PRODUCT_BUNDLE_IDENTIFIER = net.cloudwithlightning.PhageTests;
884 | PRODUCT_NAME = "$(TARGET_NAME)";
885 | SWIFT_VERSION = 5.0;
886 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Phage.app/Contents/MacOS/Phage";
887 | };
888 | name = Debug;
889 | };
890 | 0FA5318822B7D33200715AB8 /* Release */ = {
891 | isa = XCBuildConfiguration;
892 | buildSettings = {
893 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
894 | BUNDLE_LOADER = "$(TEST_HOST)";
895 | CODE_SIGN_STYLE = Automatic;
896 | COMBINE_HIDPI_IMAGES = YES;
897 | DEVELOPMENT_TEAM = S2DETNM9XB;
898 | INFOPLIST_FILE = PhageTests/Info.plist;
899 | LD_RUNPATH_SEARCH_PATHS = (
900 | "$(inherited)",
901 | "@executable_path/../Frameworks",
902 | "@loader_path/../Frameworks",
903 | );
904 | PRODUCT_BUNDLE_IDENTIFIER = net.cloudwithlightning.PhageTests;
905 | PRODUCT_NAME = "$(TARGET_NAME)";
906 | SWIFT_VERSION = 5.0;
907 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Phage.app/Contents/MacOS/Phage";
908 | };
909 | name = Release;
910 | };
911 | 0FA5318A22B7D33200715AB8 /* Debug */ = {
912 | isa = XCBuildConfiguration;
913 | buildSettings = {
914 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
915 | CODE_SIGN_STYLE = Automatic;
916 | COMBINE_HIDPI_IMAGES = YES;
917 | DEVELOPMENT_TEAM = S2DETNM9XB;
918 | INFOPLIST_FILE = PhageUITests/Info.plist;
919 | LD_RUNPATH_SEARCH_PATHS = (
920 | "$(inherited)",
921 | "@executable_path/../Frameworks",
922 | "@loader_path/../Frameworks",
923 | );
924 | PRODUCT_BUNDLE_IDENTIFIER = net.cloudwithlightning.PhageUITests;
925 | PRODUCT_NAME = "$(TARGET_NAME)";
926 | SWIFT_VERSION = 5.0;
927 | TEST_TARGET_NAME = Phage;
928 | };
929 | name = Debug;
930 | };
931 | 0FA5318B22B7D33200715AB8 /* Release */ = {
932 | isa = XCBuildConfiguration;
933 | buildSettings = {
934 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
935 | CODE_SIGN_STYLE = Automatic;
936 | COMBINE_HIDPI_IMAGES = YES;
937 | DEVELOPMENT_TEAM = S2DETNM9XB;
938 | INFOPLIST_FILE = PhageUITests/Info.plist;
939 | LD_RUNPATH_SEARCH_PATHS = (
940 | "$(inherited)",
941 | "@executable_path/../Frameworks",
942 | "@loader_path/../Frameworks",
943 | );
944 | PRODUCT_BUNDLE_IDENTIFIER = net.cloudwithlightning.PhageUITests;
945 | PRODUCT_NAME = "$(TARGET_NAME)";
946 | SWIFT_VERSION = 5.0;
947 | TEST_TARGET_NAME = Phage;
948 | };
949 | name = Release;
950 | };
951 | 0FDFB3E422B7EA8200213CC7 /* Debug */ = {
952 | isa = XCBuildConfiguration;
953 | buildSettings = {
954 | APPLICATION_EXTENSION_API_ONLY = YES;
955 | CLANG_ENABLE_MODULES = YES;
956 | CODE_SIGN_STYLE = Automatic;
957 | COMBINE_HIDPI_IMAGES = YES;
958 | CURRENT_PROJECT_VERSION = 1;
959 | DEFINES_MODULE = YES;
960 | DEVELOPMENT_TEAM = S2DETNM9XB;
961 | DYLIB_COMPATIBILITY_VERSION = 1;
962 | DYLIB_CURRENT_VERSION = 1;
963 | DYLIB_INSTALL_NAME_BASE = "@rpath";
964 | FRAMEWORK_VERSION = A;
965 | INFOPLIST_FILE = PhageCore/Info.plist;
966 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
967 | LD_RUNPATH_SEARCH_PATHS = (
968 | "$(inherited)",
969 | "@executable_path/../Frameworks",
970 | "@loader_path/Frameworks",
971 | );
972 | PRODUCT_BUNDLE_IDENTIFIER = net.cloudwithlightning.PhageCore;
973 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
974 | SKIP_INSTALL = YES;
975 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
976 | SWIFT_VERSION = 5.0;
977 | VERSIONING_SYSTEM = "apple-generic";
978 | VERSION_INFO_PREFIX = "";
979 | };
980 | name = Debug;
981 | };
982 | 0FDFB3E522B7EA8200213CC7 /* Release */ = {
983 | isa = XCBuildConfiguration;
984 | buildSettings = {
985 | APPLICATION_EXTENSION_API_ONLY = YES;
986 | CLANG_ENABLE_MODULES = YES;
987 | CODE_SIGN_STYLE = Automatic;
988 | COMBINE_HIDPI_IMAGES = YES;
989 | CURRENT_PROJECT_VERSION = 1;
990 | DEFINES_MODULE = YES;
991 | DEVELOPMENT_TEAM = S2DETNM9XB;
992 | DYLIB_COMPATIBILITY_VERSION = 1;
993 | DYLIB_CURRENT_VERSION = 1;
994 | DYLIB_INSTALL_NAME_BASE = "@rpath";
995 | FRAMEWORK_VERSION = A;
996 | INFOPLIST_FILE = PhageCore/Info.plist;
997 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
998 | LD_RUNPATH_SEARCH_PATHS = (
999 | "$(inherited)",
1000 | "@executable_path/../Frameworks",
1001 | "@loader_path/Frameworks",
1002 | );
1003 | PRODUCT_BUNDLE_IDENTIFIER = net.cloudwithlightning.PhageCore;
1004 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
1005 | SKIP_INSTALL = YES;
1006 | SWIFT_VERSION = 5.0;
1007 | VERSIONING_SYSTEM = "apple-generic";
1008 | VERSION_INFO_PREFIX = "";
1009 | };
1010 | name = Release;
1011 | };
1012 | 0FDFB3E822B7EA8200213CC7 /* Debug */ = {
1013 | isa = XCBuildConfiguration;
1014 | buildSettings = {
1015 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
1016 | CODE_SIGN_STYLE = Automatic;
1017 | COMBINE_HIDPI_IMAGES = YES;
1018 | DEVELOPMENT_TEAM = S2DETNM9XB;
1019 | INFOPLIST_FILE = PhageCoreTests/Info.plist;
1020 | LD_RUNPATH_SEARCH_PATHS = (
1021 | "$(inherited)",
1022 | "@executable_path/../Frameworks",
1023 | "@loader_path/../Frameworks",
1024 | );
1025 | PRODUCT_BUNDLE_IDENTIFIER = net.cloudwithlightning.PhageCoreTests;
1026 | PRODUCT_NAME = "$(TARGET_NAME)";
1027 | SWIFT_VERSION = 5.0;
1028 | };
1029 | name = Debug;
1030 | };
1031 | 0FDFB3E922B7EA8200213CC7 /* Release */ = {
1032 | isa = XCBuildConfiguration;
1033 | buildSettings = {
1034 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
1035 | CODE_SIGN_STYLE = Automatic;
1036 | COMBINE_HIDPI_IMAGES = YES;
1037 | DEVELOPMENT_TEAM = S2DETNM9XB;
1038 | INFOPLIST_FILE = PhageCoreTests/Info.plist;
1039 | LD_RUNPATH_SEARCH_PATHS = (
1040 | "$(inherited)",
1041 | "@executable_path/../Frameworks",
1042 | "@loader_path/../Frameworks",
1043 | );
1044 | PRODUCT_BUNDLE_IDENTIFIER = net.cloudwithlightning.PhageCoreTests;
1045 | PRODUCT_NAME = "$(TARGET_NAME)";
1046 | SWIFT_VERSION = 5.0;
1047 | };
1048 | name = Release;
1049 | };
1050 | /* End XCBuildConfiguration section */
1051 |
1052 | /* Begin XCConfigurationList section */
1053 | 0F31D90B22B7DE26009D305A /* Build configuration list for PBXNativeTarget "Phage Injector" */ = {
1054 | isa = XCConfigurationList;
1055 | buildConfigurations = (
1056 | 0F31D90922B7DE26009D305A /* Debug */,
1057 | 0F31D90A22B7DE26009D305A /* Release */,
1058 | );
1059 | defaultConfigurationIsVisible = 0;
1060 | defaultConfigurationName = Release;
1061 | };
1062 | 0FA5315522B7D33000715AB8 /* Build configuration list for PBXProject "Phage" */ = {
1063 | isa = XCConfigurationList;
1064 | buildConfigurations = (
1065 | 0FA5318122B7D33200715AB8 /* Debug */,
1066 | 0FA5318222B7D33200715AB8 /* Release */,
1067 | );
1068 | defaultConfigurationIsVisible = 0;
1069 | defaultConfigurationName = Release;
1070 | };
1071 | 0FA5318322B7D33200715AB8 /* Build configuration list for PBXNativeTarget "Phage" */ = {
1072 | isa = XCConfigurationList;
1073 | buildConfigurations = (
1074 | 0FA5318422B7D33200715AB8 /* Debug */,
1075 | 0FA5318522B7D33200715AB8 /* Release */,
1076 | );
1077 | defaultConfigurationIsVisible = 0;
1078 | defaultConfigurationName = Release;
1079 | };
1080 | 0FA5318622B7D33200715AB8 /* Build configuration list for PBXNativeTarget "PhageTests" */ = {
1081 | isa = XCConfigurationList;
1082 | buildConfigurations = (
1083 | 0FA5318722B7D33200715AB8 /* Debug */,
1084 | 0FA5318822B7D33200715AB8 /* Release */,
1085 | );
1086 | defaultConfigurationIsVisible = 0;
1087 | defaultConfigurationName = Release;
1088 | };
1089 | 0FA5318922B7D33200715AB8 /* Build configuration list for PBXNativeTarget "PhageUITests" */ = {
1090 | isa = XCConfigurationList;
1091 | buildConfigurations = (
1092 | 0FA5318A22B7D33200715AB8 /* Debug */,
1093 | 0FA5318B22B7D33200715AB8 /* Release */,
1094 | );
1095 | defaultConfigurationIsVisible = 0;
1096 | defaultConfigurationName = Release;
1097 | };
1098 | 0FDFB3E322B7EA8200213CC7 /* Build configuration list for PBXNativeTarget "PhageCore" */ = {
1099 | isa = XCConfigurationList;
1100 | buildConfigurations = (
1101 | 0FDFB3E422B7EA8200213CC7 /* Debug */,
1102 | 0FDFB3E522B7EA8200213CC7 /* Release */,
1103 | );
1104 | defaultConfigurationIsVisible = 0;
1105 | defaultConfigurationName = Release;
1106 | };
1107 | 0FDFB3E722B7EA8200213CC7 /* Build configuration list for PBXNativeTarget "PhageCoreTests" */ = {
1108 | isa = XCConfigurationList;
1109 | buildConfigurations = (
1110 | 0FDFB3E822B7EA8200213CC7 /* Debug */,
1111 | 0FDFB3E922B7EA8200213CC7 /* Release */,
1112 | );
1113 | defaultConfigurationIsVisible = 0;
1114 | defaultConfigurationName = Release;
1115 | };
1116 | /* End XCConfigurationList section */
1117 | };
1118 | rootObject = 0FA5315222B7D33000715AB8 /* Project object */;
1119 | }
1120 |
--------------------------------------------------------------------------------
/Phage.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Phage.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Phage/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Phage
4 | //
5 | // Created by cpsdqs on 2019-06-17.
6 | // Copyright © 2019 cpsdqs. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import SwiftUI
11 | import PhageCore
12 |
13 | @NSApplicationMain
14 | class AppDelegate : NSObject, NSApplicationDelegate {
15 |
16 | var window: NSWindow!
17 |
18 | func applicationDidFinishLaunching(_ aNotification: Notification) {
19 | window = NSWindow(
20 | contentRect: NSRect(x: 0, y: 0, width: 600, height: 480),
21 | styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
22 | backing: .buffered, defer: false)
23 | window.center()
24 | window.setFrameAutosaveName("Main Window")
25 | window.title = "Phage"
26 |
27 | window.contentView = NSHostingView(rootView: ContentView())
28 |
29 | window.makeKeyAndOrderFront(nil)
30 | }
31 |
32 | func applicationWillTerminate(_ aNotification: Notification) {
33 | }
34 |
35 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
36 | return true
37 | }
38 |
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/Phage/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon_16x16.png",
5 | "idiom" : "mac",
6 | "scale" : "1x",
7 | "size" : "16x16"
8 | },
9 | {
10 | "filename" : "icon_16x16@2x.png",
11 | "idiom" : "mac",
12 | "scale" : "2x",
13 | "size" : "16x16"
14 | },
15 | {
16 | "filename" : "icon_32x32.png",
17 | "idiom" : "mac",
18 | "scale" : "1x",
19 | "size" : "32x32"
20 | },
21 | {
22 | "filename" : "icon_32x32@2x.png",
23 | "idiom" : "mac",
24 | "scale" : "2x",
25 | "size" : "32x32"
26 | },
27 | {
28 | "filename" : "icon_128x128.png",
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "filename" : "icon_128x128@2x.png",
35 | "idiom" : "mac",
36 | "scale" : "2x",
37 | "size" : "128x128"
38 | },
39 | {
40 | "filename" : "icon_256x256.png",
41 | "idiom" : "mac",
42 | "scale" : "1x",
43 | "size" : "256x256"
44 | },
45 | {
46 | "filename" : "icon_256x256@2x.png",
47 | "idiom" : "mac",
48 | "scale" : "2x",
49 | "size" : "256x256"
50 | },
51 | {
52 | "filename" : "icon_512x512.png",
53 | "idiom" : "mac",
54 | "scale" : "1x",
55 | "size" : "512x512"
56 | },
57 | {
58 | "filename" : "icon_512x512@2x.png",
59 | "idiom" : "mac",
60 | "scale" : "2x",
61 | "size" : "512x512"
62 | }
63 | ],
64 | "info" : {
65 | "author" : "xcode",
66 | "version" : 1
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Phage/Assets.xcassets/AppIcon.appiconset/icon_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage/Assets.xcassets/AppIcon.appiconset/icon_128x128.png
--------------------------------------------------------------------------------
/Phage/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png
--------------------------------------------------------------------------------
/Phage/Assets.xcassets/AppIcon.appiconset/icon_16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage/Assets.xcassets/AppIcon.appiconset/icon_16x16.png
--------------------------------------------------------------------------------
/Phage/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png
--------------------------------------------------------------------------------
/Phage/Assets.xcassets/AppIcon.appiconset/icon_256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage/Assets.xcassets/AppIcon.appiconset/icon_256x256.png
--------------------------------------------------------------------------------
/Phage/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png
--------------------------------------------------------------------------------
/Phage/Assets.xcassets/AppIcon.appiconset/icon_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage/Assets.xcassets/AppIcon.appiconset/icon_32x32.png
--------------------------------------------------------------------------------
/Phage/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png
--------------------------------------------------------------------------------
/Phage/Assets.xcassets/AppIcon.appiconset/icon_512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage/Assets.xcassets/AppIcon.appiconset/icon_512x512.png
--------------------------------------------------------------------------------
/Phage/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cpsdqs/Phage/191f43b9b34cd5e675b67a77741c80ab1486de97/Phage/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png
--------------------------------------------------------------------------------
/Phage/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Phage/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
--------------------------------------------------------------------------------
/Phage/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // Phage
4 | //
5 | // Created by cpsdqs on 2019-06-17.
6 | // Copyright © 2019 cpsdqs. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 | import PhageCore
11 |
12 | struct ContentView : View {
13 | @ObservedObject var data = PhageData()
14 |
15 | private func sortedBundles() -> [PhageDataBundle] {
16 | return data.bundles.sorted(by: { (arg0, arg1) -> Bool in
17 | let (lhs, _) = arg0
18 | let (rhs, _) = arg1
19 | return lhs.lexicographicallyPrecedes(rhs)
20 | }).map({ (arg0) -> PhageDataBundle in
21 | let (_, value) = arg0
22 | return value
23 | })
24 | }
25 |
26 | var body: some View {
27 | NavigationView {
28 | List {
29 | HStack(alignment: .firstTextBaseline) {
30 | Text("Bundles").font(.title).bold()
31 | Spacer()
32 | Button(action: {
33 | NSWorkspace.shared.open(bundlesURL)
34 | }) {
35 | Text("Show in Finder")
36 | }
37 | }
38 | ForEach(sortedBundles()) { bundle in SidebarBundle(bundle: bundle, data: self.data) }
39 | if data.bundles.count == 0 {
40 | HStack {
41 | Spacer()
42 | Text("No bundles").foregroundColor(.gray)
43 | Spacer()
44 | }
45 | }
46 | }.frame(width: 300)
47 | }
48 | }
49 | }
50 |
51 | struct SidebarBundle : View {
52 | private struct BundleToggle : View {
53 | @Binding var enabled: Bool
54 | var body: some View {
55 | // using the checkbox toggle style doesn't play nice with the computed binding for
56 | // some reason
57 | Toggle(isOn: $enabled) {
58 | EmptyView()
59 | }.toggleStyle(SwitchToggleStyle())
60 | }
61 | }
62 |
63 | @ObservedObject var bundle: PhageDataBundle
64 | @State var hackyDisabledProxy = false
65 | var data: PhageData
66 |
67 | var body: some View {
68 | NavigationLink(
69 | destination:
70 | BundleView(dependencies: self.data.dependencies, bundle: bundle)
71 | .frame(minWidth: 300)
72 | ) {
73 | HStack {
74 | VStack(alignment: .leading) {
75 | Text(bundle.url.lastPathComponent).foregroundColor(hackyDisabledProxy ? .secondary : nil)
76 | if !hackyDisabledProxy {
77 | Text("Files: \(bundle.files.count)").foregroundColor(.secondary)
78 | }
79 | }.frame(minHeight: 32).animation(.easeOut)
80 | Spacer()
81 | BundleToggle(enabled: Binding(get: {
82 | !self.bundle.disabled
83 | }, set: { enabled in
84 | self.bundle.disabled = !enabled
85 | self.hackyDisabledProxy = self.bundle.disabled
86 | })).onAppear {
87 | self.hackyDisabledProxy = self.bundle.disabled
88 | }
89 | }
90 | }
91 | }
92 | }
93 |
94 | struct BundleView : View {
95 | private struct MaybeNone : View {
96 | var count: Int
97 | var text: String
98 | var body: some View {
99 | if count == 0 {
100 | return AnyView(Text(text).frame(maxWidth: .infinity).padding(.bottom))
101 | } else {
102 | return AnyView(EmptyView())
103 | }
104 | }
105 | }
106 |
107 | var dependencies: PhageDependencies
108 | @ObservedObject var bundle: PhageDataBundle
109 |
110 | var body: some View {
111 | List {
112 | HStack(alignment: .lastTextBaseline) {
113 | Text(bundle.url.lastPathComponent).font(.title).bold().padding(.top)
114 | Spacer()
115 | Button(action: {
116 | NSWorkspace.shared.open(self.bundle.url)
117 | }) {
118 | Text("Open")
119 | }
120 | }
121 | Spacer()
122 |
123 | Text("Files").font(.headline).bold()
124 | MaybeNone(count: bundle.files.count, text: "No files")
125 | ForEach(Array(bundle.files.values.sorted(by: { a, b in
126 | a.url.lastPathComponent.lexicographicallyPrecedes(b.url.lastPathComponent)
127 | }))) { item in
128 | BundleFileView(file: item)
129 | }
130 | Spacer()
131 |
132 | Text("Dependencies").font(.headline).bold()
133 | MaybeNone(count: bundle.dependencies().count, text: "No dependencies")
134 | ForEach(bundle.dependencies().map { IdURL($0) }) { item in
135 | DependencyView(dependencies: self.dependencies, url: item.url)
136 | }
137 | }
138 | }
139 | }
140 |
141 | struct BundleFileView : View {
142 | var file: PhageDataFile
143 |
144 | var body: some View {
145 | VStack(alignment: .leading) {
146 | HStack {
147 | Text(file.url.lastPathComponent).bold()
148 | Spacer()
149 | Button(action: {
150 | NSWorkspace.shared.openFile(self.file.url.path)
151 | }) {
152 | Text("Edit")
153 | }
154 | }
155 | FileContents(contents: file.contents())
156 | if file.type == .javascript {
157 | ScriptFileAttributes(file: file)
158 | }
159 | }.padding()
160 | .background(Color(NSColor.controlBackgroundColor).cornerRadius(8))
161 | }
162 | }
163 |
164 | private struct ForEachIdTag {
165 | var id: Int
166 | var data: T
167 | }
168 |
169 | struct FileContents : View {
170 | var contents: PhageDataFile.Contents?
171 | var body: some View {
172 | switch contents {
173 | case .none:
174 | return AnyView(Text("could not read file contents").foregroundColor(.secondary))
175 | case .some(.javascript(let section)):
176 | return AnyView(SectionView(section: section))
177 | case .some(.stylesheets(let sections)):
178 | if sections.isEmpty {
179 | return AnyView(Text("no stylesheet sections").foregroundColor(.secondary))
180 | }
181 | return AnyView(VStack(alignment: .leading) {
182 | ForEach(sections.enumerated().map({ (i, s) in ForEachIdTag(id: i, data: s) }), id: \.id) { item in
183 | SectionView(stylesheetNumber: item.id + 1, section: item.data)
184 | }
185 | })
186 | }
187 | }
188 | }
189 |
190 | struct SectionView : View {
191 | private struct Title: View {
192 | var number: Int?
193 | var body: some View {
194 | if let number = number {
195 | return Text("Stylesheet Section #\(number)")
196 | } else {
197 | return Text("Script")
198 | }
199 | }
200 | }
201 |
202 | var stylesheetNumber: Int?
203 | var section: PhageCore.Section
204 |
205 | var body: some View {
206 | VStack(alignment: .leading) {
207 | Title(number: stylesheetNumber)
208 | VStack(alignment: .leading) {
209 | ForEach(section.rules.enumerated().map({ (i, s) in ForEachIdTag(id: i, data: s) }), id: \.id) { rule in
210 | SectionMatchRule(rule: rule.data)
211 | }
212 | }.padding(.leading).padding(.top, 4)
213 | }
214 | }
215 | }
216 |
217 | struct SectionMatchRule : View {
218 | private struct Tag : View {
219 | var text: String
220 | var body: some View {
221 | Text(text)
222 | .font(.system(size: 10))
223 | .padding(2)
224 | .cornerRadius(4)
225 | .background(Color(NSColor.windowBackgroundColor).cornerRadius(4))
226 | }
227 | }
228 | private struct Value : View {
229 | var value: String
230 | var body: some View {
231 | Text(value)
232 | .font(.system(size: 11, weight: .regular, design: .monospaced))
233 | .padding(2)
234 | }
235 | }
236 |
237 | var rule: PhageCore.MatchRule
238 |
239 | var body: some View {
240 | switch rule {
241 | case .domain(let domain):
242 | return HStack { Tag(text: "Domain"); Value(value: domain) }
243 | case .exact(let exact):
244 | return HStack { Tag(text: "Exact"); Value(value: exact) }
245 | case .glob(let glob):
246 | return HStack { Tag(text: "Glob"); Value(value: glob) }
247 | case .prefix(let prefix):
248 | return HStack { Tag(text: "Prefix"); Value(value: prefix) }
249 | case .regexp(let regexp):
250 | return HStack { Tag(text: "RegExp"); Value(value: regexp) }
251 | }
252 | }
253 | }
254 |
255 | struct ScriptFileAttributes : View {
256 | var file: PhageDataFile
257 |
258 | var body: some View {
259 | VStack(alignment: .leading) {
260 | Toggle("Inject into page context", isOn: Binding(get: {
261 | file.inPageContext
262 | }, set: { val in
263 | file.inPageContext = val
264 | }))
265 | }
266 | }
267 | }
268 |
269 | struct DependencyView : View {
270 | var dependencies: PhageDependencies
271 | var url: URL
272 |
273 | @State var loading = false
274 | @State var loaded = false
275 |
276 | var body: some View {
277 | HStack {
278 | VStack(alignment: .leading) {
279 | Text(url.lastPathComponent)
280 | Text(url.absoluteString).font(Font.system(size: 10))
281 | }
282 | Spacer()
283 | if loading {
284 | Text("Loading")
285 | } else if loaded {
286 | Text("Loaded")
287 | }
288 | }.onTapGesture {
289 | debugPrint("Loading dependency \(self.url)")
290 | self.loading = true
291 | self.dependencies.loadDependency(at: self.url) { data in
292 | if data != nil {
293 | debugPrint("Loaded")
294 | self.loaded = true
295 | self.loading = false
296 | } else {
297 | debugPrint("Failed to load")
298 | self.loading = false
299 | }
300 | }
301 | }.onAppear {
302 | self.loaded = self.dependencies.getDependencyContents(of: self.url) != nil
303 | }
304 | }
305 | }
306 |
307 | #if DEBUG
308 | struct ContentView_Previews : PreviewProvider {
309 | static var previews: some View {
310 | ContentView()
311 | }
312 | }
313 | #endif
314 |
315 | class IdURL : Identifiable {
316 | var url: URL
317 |
318 | var id: String {
319 | return url.absoluteString
320 | }
321 |
322 | init(_ url: URL) {
323 | self.url = url
324 | }
325 | }
326 |
--------------------------------------------------------------------------------
/Phage/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSApplicationCategoryType
24 | public.app-category.utilities
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSHumanReadableCopyright
28 | Copyright © 2019 cpsdqs. All rights reserved.
29 | NSMainStoryboardFile
30 | Main
31 | NSPrincipalClass
32 | NSApplication
33 | NSSupportsAutomaticTermination
34 |
35 | NSSupportsSuddenTermination
36 |
37 | TeamIdentifierPrefix
38 | $(TeamIdentifierPrefix)
39 |
40 |
41 |
--------------------------------------------------------------------------------
/Phage/Phage.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.application-groups
8 |
9 | $(TeamIdentifierPrefix)net.cloudwithlightning.phage
10 |
11 | com.apple.security.network.client
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Phage/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/PhageCore/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSHumanReadableCopyright
22 | Copyright © 2019 cpsdqs. All rights reserved.
23 | TeamIdentifierPrefix
24 | $(TeamIdentifierPrefix)
25 |
26 |
27 |
--------------------------------------------------------------------------------
/PhageCore/PhageData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhageData.swift
3 | // PhageCore
4 | //
5 | // Created by cpsdqs on 2019-06-17.
6 | // Copyright © 2019 cpsdqs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Combine
11 |
12 | public let appGroupID = Bundle.main.infoDictionary!["TeamIdentifierPrefix"] as! String + "net.cloudwithlightning.phage"
13 | public let bundlesURL = FileManager.default
14 | .containerURL(forSecurityApplicationGroupIdentifier: appGroupID)!
15 | .appendingPathComponent("bundles")
16 | public let dependenciesURL = FileManager.default
17 | .containerURL(forSecurityApplicationGroupIdentifier: appGroupID)!
18 | .appendingPathComponent("dependencies")
19 |
20 | /// Observes Phage user data, i.e. scripts and stylesheets.
21 | public class PhageData : NSObject, NSFilePresenter, ObservableObject {
22 |
23 | public var dependencies: PhageDependencies! = nil
24 |
25 | public override init() {
26 | super.init()
27 |
28 | dependencies = PhageDependencies(owner: self)
29 | NSFileCoordinator.addFilePresenter(self)
30 |
31 | reload()
32 | }
33 |
34 | // MARK: - Bundle Handling
35 |
36 | @Published public var bundles: [String:PhageDataBundle] = [:]
37 |
38 | /// Reloads all bundles.
39 | func reload() {
40 | bundles = [:]
41 |
42 | let directoryContents = try? FileManager.default.contentsOfDirectory(
43 | at: bundlesURL,
44 | includingPropertiesForKeys: nil,
45 | options: .skipsHiddenFiles
46 | )
47 |
48 | if let directoryContents = directoryContents {
49 | for subitemURL in directoryContents {
50 | let _ = tryEnsureBundle(enclosing: subitemURL)
51 | }
52 | }
53 | }
54 |
55 | /// Returns the enclosing bundle’s URL and the remaining subpath.
56 | func bundleURLAndSubpath(enclosing url: URL) -> (URL, [String])? {
57 | var pathComponents = url.pathComponents
58 | for component in bundlesURL.pathComponents {
59 | // check if path matches and pop front
60 | if pathComponents.first == component {
61 | pathComponents.removeFirst()
62 | } else {
63 | return nil
64 | }
65 | }
66 | if pathComponents.isEmpty {
67 | return nil
68 | }
69 | let bundleURL = bundlesURL.appendingPathComponent(pathComponents.removeFirst())
70 | return (bundleURL, pathComponents)
71 | }
72 |
73 | func bundleName(for bundleURL: URL) -> String {
74 | return bundleURL.lastPathComponent
75 | }
76 |
77 | func tryEnsureBundle(enclosing url: URL) -> Bool {
78 | if let (bundleURL, _) = bundleURLAndSubpath(enclosing: url) {
79 | let name = bundleName(for: bundleURL)
80 | if bundles[name] == nil {
81 | if let bundle = PhageDataBundle(at: bundleURL) {
82 | bundles[name] = bundle
83 | return true
84 | }
85 | } else {
86 | return true
87 | }
88 | }
89 | return false
90 | }
91 |
92 | func didDeleteBundle(at url: URL) {
93 | let name = bundleName(for: url)
94 | if bundles[name] != nil {
95 | bundles.removeValue(forKey: name)
96 | }
97 | }
98 |
99 | func didDeleteBundleFile(at bundleURL: URL, subPath: [String]) {
100 | let name = bundleName(for: bundleURL)
101 | if tryEnsureBundle(enclosing: bundleURL) {
102 | if subPath.count == 1 {
103 | // TODO: what about deeper items?
104 | bundles[name]!.deletedFile(at: bundleURL.appendingPathComponent(subPath[0]))
105 | }
106 | }
107 | }
108 |
109 | func didChangeBundleFile(at bundleURL: URL, subPath: [String]) {
110 | let name = bundleName(for: bundleURL)
111 | if tryEnsureBundle(enclosing: bundleURL) {
112 | if subPath.count == 1 {
113 | // TODO: what about deeper items?
114 | bundles[name]!.updatedFile(at: bundleURL.appendingPathComponent(subPath[0]))
115 | }
116 | }
117 | }
118 |
119 | // MARK: - NSFilePresenter
120 |
121 | public var presentedItemURL: URL? {
122 | get {
123 | return bundlesURL
124 | }
125 | }
126 | public var presentedItemOperationQueue: OperationQueue = OperationQueue.main
127 |
128 | public func accommodatePresentedItemDeletion(completionHandler: @escaping (Error?) -> Void) {
129 | reload()
130 | }
131 | public func presentedItemDidMove(to newURL: URL) {
132 | reload()
133 | }
134 | public func presentedItemDidChange() {
135 | reload()
136 | }
137 |
138 | public func accommodatePresentedSubitemDeletion(at url: URL, completionHandler: @escaping (Error?) -> Void) {
139 | if let (bundleURL, subPath) = bundleURLAndSubpath(enclosing: url) {
140 | if subPath.isEmpty {
141 | // bundle has been deleted
142 | didDeleteBundle(at: bundleURL)
143 | } else {
144 | didDeleteBundleFile(at: bundleURL, subPath: subPath)
145 | }
146 | }
147 | }
148 | public func presentedSubitemDidAppear(at url: URL) {
149 | // just forward to didChange because it’s handled the same way
150 | presentedSubitemDidChange(at: url)
151 | }
152 | public func presentedSubitem(at oldURL: URL, didMoveTo newURL: URL) {
153 | accommodatePresentedSubitemDeletion(at: oldURL) { _ in }
154 | // this is fine because presentedSubitemDidChange(at:) checks if it’s a valid url
155 | presentedSubitemDidChange(at: newURL)
156 | }
157 | public func presentedSubitemDidChange(at url: URL) {
158 | // may have been deleted instead
159 | if !FileManager.default.fileExists(atPath: url.path) {
160 | accommodatePresentedSubitemDeletion(at: url, completionHandler: { _ in })
161 | return
162 | }
163 |
164 | if let (bundleURL, subPath) = bundleURLAndSubpath(enclosing: url) {
165 | if subPath.isEmpty {
166 | // a bundle was added (or changed, somehow?)
167 | let _ = tryEnsureBundle(enclosing: bundleURL)
168 | } else {
169 | didChangeBundleFile(at: bundleURL, subPath: subPath)
170 | }
171 | }
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/PhageCore/PhageDataBundle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhageDataBundle.swift
3 | // PhageCore
4 | //
5 | // Created by cpsdqs on 2019-06-18.
6 | // Copyright © 2019 cpsdqs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Combine
11 |
12 | public class PhageDataBundle : NSObject, ObservableObject, Identifiable {
13 |
14 | public let url: URL
15 | @Published public var files: [String:PhageDataFile] = [:]
16 | public var disabled: Bool {
17 | get {
18 | // check if the disabled xattr exists
19 | return url.withUnsafeFileSystemRepresentation { path in
20 | let code = getxattr(path, "disabled", nil, 0, 0, 0)
21 | // if getxattr fails that probably means the attribute doesn't exist
22 | return code != -1
23 | }
24 | }
25 | set(disabled) {
26 | url.withUnsafeFileSystemRepresentation { path in
27 | if disabled {
28 | let value = "yes".data(using: .utf8)!
29 | let result = value.withUnsafeBytes { data in
30 | setxattr(path, "disabled", data.baseAddress, data.count, 0, 0)
31 | }
32 | if result < 0 {
33 | NSLog("Failed to set disabled state on bundle at \(url)")
34 | }
35 | } else {
36 | removexattr(path, "disabled", 0)
37 | }
38 | }
39 | }
40 | }
41 |
42 | init?(at url: URL) {
43 | self.url = url
44 |
45 | super.init()
46 |
47 | guard let contents = try? FileManager.default.contentsOfDirectory(
48 | at: url,
49 | includingPropertiesForKeys: nil,
50 | options: .skipsHiddenFiles
51 | ) else { return nil }
52 |
53 | for subitemURL in contents {
54 | updatedFile(at: subitemURL)
55 | }
56 | }
57 |
58 | public func dependencies() -> [URL] {
59 | var dependencies: [URL] = []
60 | for (_, file) in files {
61 | dependencies.append(contentsOf: file.dependencies()
62 | .map { URL(string: $0) }
63 | .filter { $0 != nil }
64 | .map { $0! })
65 | }
66 | return dependencies
67 | }
68 |
69 | /// Handles a new or changed file.
70 | /// - Parameter url: a top-level subitem of this bundle. Will not be checked for validity
71 | func updatedFile(at url: URL) {
72 | let name = url.lastPathComponent
73 |
74 | if let file = PhageDataFile(at: url) {
75 | files[name] = file
76 | }
77 | }
78 |
79 | func deletedFile(at url: URL) {
80 | let name = url.lastPathComponent
81 | files.removeValue(forKey: name)
82 | }
83 |
84 | // MARK: - Identifiable
85 | public var id: URL {
86 | get {
87 | return url
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/PhageCore/PhageDataFile.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhageDataFile.swift
3 | // PhageCore
4 | //
5 | // Created by cpsdqs on 2019-06-18.
6 | // Copyright © 2019 cpsdqs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class PhageDataFile : NSObject, Identifiable {
12 | public let url: URL
13 | public let type: FileType
14 |
15 | init?(at url: URL) {
16 | let pathExtension = url.pathExtension
17 |
18 | let type: PhageDataFile.FileType
19 |
20 | switch pathExtension {
21 | case "js":
22 | type = .javascript
23 | case "css":
24 | type = .stylesheet
25 | default:
26 | // unsupported
27 | return nil
28 | }
29 |
30 | self.url = url
31 | self.type = type
32 | }
33 |
34 | var cachedContents: Contents?
35 | var cachedDependencies: [String]?
36 |
37 | public func contents() -> Contents? {
38 | if let contents = cachedContents {
39 | return contents
40 | }
41 |
42 | guard let rawContents = loadRawContents() else { return nil }
43 |
44 | switch type {
45 | case .javascript:
46 | if let meta = parseScriptMetadata(for: rawContents) {
47 | cachedContents = .javascript(Section(rules: meta.matches, contents: rawContents))
48 | cachedDependencies = meta.requires
49 | }
50 | case .stylesheet:
51 | cachedContents = .stylesheets(parseCSSFileSections(rawContents))
52 | }
53 |
54 | return cachedContents
55 | }
56 |
57 | public func dependencies() -> [String] {
58 | let _ = contents()
59 | return cachedDependencies ?? []
60 | }
61 |
62 | func loadRawContents() -> String? {
63 | if let data = FileManager.default.contents(atPath: url.path) {
64 | if let string = String(data: data, encoding: .utf8) {
65 | return string
66 | }
67 | }
68 |
69 | return nil
70 | }
71 |
72 | public var inPageContext: Bool {
73 | get {
74 | // check if the disabled xattr exists
75 | return url.withUnsafeFileSystemRepresentation { path in
76 | let code = getxattr(path, "inPageContext", nil, 0, 0, 0)
77 | // if getxattr fails that probably means the attribute doesn't exist
78 | return code != -1
79 | }
80 | }
81 | set(inPageContext) {
82 | url.withUnsafeFileSystemRepresentation { path in
83 | if inPageContext {
84 | let value = "yes".data(using: .utf8)!
85 | let result = value.withUnsafeBytes { data in
86 | setxattr(path, "inPageContext", data.baseAddress, data.count, 0, 0)
87 | }
88 | if result < 0 {
89 | NSLog("Failed to set inPageContext flag on file at \(url)")
90 | }
91 | } else {
92 | removexattr(path, "inPageContext", 0)
93 | }
94 | }
95 | }
96 | }
97 |
98 | // MARK: - Identifiable
99 | public var id: URL {
100 | get {
101 | return url
102 | }
103 | }
104 |
105 | public enum FileType {
106 | case javascript
107 | case stylesheet
108 | }
109 |
110 | public enum Contents {
111 | case javascript(Section)
112 | case stylesheets([Section])
113 | }
114 | }
115 |
116 | /// Various types of URL matching.
117 | public enum MatchRule: Equatable {
118 | /// Matches a glob, such as `https://example.com/**/test`
119 | case glob(String)
120 | /// Matches an exact URL.
121 | case exact(String)
122 | /// Matches if the URL starts with the given prefix.
123 | case prefix(String)
124 | /// Matches if the URL is on the given domain or subdomain.
125 | case domain(String)
126 | /// Matches using the regular expression.
127 | case regexp(String)
128 |
129 | func match(_ url: URL) -> Bool {
130 | switch self {
131 | case .glob(let pattern):
132 | let regex = globToRegex(pattern)
133 | return MatchRule.regexp(regex).match(url)
134 | case .exact(let pattern):
135 | return url.absoluteString == pattern
136 | case .prefix(let pattern):
137 | return url.absoluteString.starts(with: pattern)
138 | case .domain(let pattern):
139 | return url.host == pattern || (url.host?.hasSuffix("." + pattern) ?? false)
140 | case .regexp(let pattern):
141 | if let regexp = try? NSRegularExpression(pattern: pattern, options: .init()) {
142 | let urlString = url.absoluteString
143 | return regexp.firstMatch(in: urlString, options: .init(), range: NSMakeRange(0, urlString.count)) != nil
144 | } else {
145 | return false
146 | }
147 | }
148 | }
149 | }
150 |
151 | func globToRegex(_ glob: String) -> String {
152 | var regex = ""
153 |
154 | var index = glob.startIndex
155 | var mayDoDoubleStar = true
156 | while index < glob.endIndex {
157 | let c = glob[index]
158 | switch c {
159 | case "?":
160 | regex.append("[^/]")
161 | mayDoDoubleStar = false
162 | case "*":
163 | if mayDoDoubleStar {
164 | if glob[index...].starts(with: "**/") || glob[index...] == "**" {
165 | regex.append("[^/]*(?:/[^/])*")
166 | index = glob.index(index, offsetBy: 2)
167 | mayDoDoubleStar = false
168 | continue
169 | }
170 | }
171 | regex.append("[^/]*")
172 | case "/":
173 | regex.append("/")
174 | mayDoDoubleStar = true
175 | case "[":
176 | var i = glob.index(index, offsetBy: 1)
177 | var chars = ""
178 | while i < glob.endIndex && (glob[i] != "]" || chars.isEmpty) {
179 | let c = glob[i]
180 | if chars.isEmpty && c == "!" {
181 | chars.append("^")
182 | } else {
183 | // TODO: ranges
184 | switch c {
185 | case "\\":
186 | chars.append("\\\\")
187 | case "^":
188 | chars.append("\\^")
189 | default:
190 | chars.append(c)
191 | }
192 | }
193 | i = glob.index(i, offsetBy: 1)
194 | }
195 | if i < glob.endIndex && glob[i] == "]" {
196 | regex.append("[\(chars)]")
197 | index = glob.index(i, offsetBy: 1)
198 | continue
199 | }
200 | default:
201 | regex.append(c)
202 | }
203 | index = glob.index(index, offsetBy: 1)
204 | }
205 |
206 | return regex
207 | }
208 |
209 | public struct Section {
210 | public let rules: [MatchRule]
211 | public let contents: String
212 |
213 | public func matches(url: URL) -> Bool {
214 | if rules.isEmpty {
215 | return true
216 | }
217 | for rule in rules {
218 | if rule.match(url) {
219 | return true
220 | }
221 | }
222 | return false
223 | }
224 | }
225 |
226 | public struct ScriptMeta {
227 | var name = "(unnamed)"
228 | var namespace = "?"
229 | var description = "?"
230 | var version = "?"
231 | var author = "?"
232 | var matches: [MatchRule] = []
233 | var requires: [String] = []
234 | }
235 |
236 | /// A simple CSS parser that extracts @document sections.
237 | func parseCSSFileSections(_ contents: String) -> [Section] {
238 | var sectionRanges: [Range] = []
239 |
240 | func consumeComment(in string: String, at startIndex: String.Index) -> String.Index? {
241 | let substr = string[startIndex...]
242 | if substr.starts(with: "/*") {
243 | return substr.range(of: "*/")?.upperBound
244 | }
245 | return nil
246 | }
247 |
248 | func consumeString(in string: String, at startIndex: String.Index, escaping delimiter: Character) -> String.Index? {
249 | var index = startIndex
250 | while index < string.endIndex {
251 | let c = string[index]
252 | switch c {
253 | case "\\":
254 | // escape next character
255 | index = string.index(index, offsetBy: 1)
256 | case delimiter:
257 | // end of string
258 | return index
259 | default:
260 | break
261 | }
262 | index = string.index(index, offsetBy: 1)
263 | }
264 | return nil
265 | }
266 |
267 | func matchAtDocumentToken(at index: String.Index) -> String.Index? {
268 | let options = ["@document", "@-moz-document"]
269 | let substr = contents[index...]
270 | for option in options {
271 | if substr.starts(with: option) {
272 | return contents.index(index, offsetBy: option.count)
273 | }
274 | }
275 | return nil
276 | }
277 |
278 | func consumeTillMatchingBrace(at startIndex: String.Index) -> String.Index {
279 | var braceDepth = 0
280 |
281 | var index = startIndex
282 | while index < contents.endIndex {
283 | let c = contents[index]
284 | if let newIndex = consumeComment(in: contents, at: index) {
285 | index = newIndex
286 | continue
287 | }
288 | switch c {
289 | case "{":
290 | braceDepth += 1
291 | case "}":
292 | braceDepth -= 1
293 | if braceDepth <= 0 {
294 | return contents.index(index, offsetBy: 1)
295 | }
296 | case "'":
297 | let startIndex = contents.index(index, offsetBy: 1)
298 | if let newIndex = consumeString(in: contents, at: startIndex, escaping: "'") {
299 | index = contents.index(newIndex, offsetBy: 1)
300 | continue
301 | }
302 | case "\"":
303 | let startIndex = contents.index(index, offsetBy: 1)
304 | if let newIndex = consumeString(in: contents, at: startIndex, escaping: "\"") {
305 | index = contents.index(newIndex, offsetBy: 1)
306 | continue
307 | }
308 | default:
309 | break
310 | }
311 | index = contents.index(index, offsetBy: 1)
312 | }
313 | return index
314 | }
315 |
316 | var index = contents.startIndex
317 | while index < contents.endIndex {
318 | let c = contents[index]
319 |
320 | if let newIndex = consumeComment(in: contents, at: index) {
321 | index = newIndex
322 | continue
323 | }
324 |
325 | if !c.isWhitespace {
326 | if let newIndex = matchAtDocumentToken(at: index) {
327 | let lowerBound = newIndex
328 | let upperBound = consumeTillMatchingBrace(at: newIndex)
329 | index = upperBound
330 | sectionRanges.append(lowerBound.. ScriptMeta? {
479 | let startRe = try! NSRegularExpression(pattern: "^//\\s*==UserScript==$", options: [])
480 | let endRe = try! NSRegularExpression(pattern: "^//\\s*==/UserScript==$", options: [])
481 | var inMetaBlock = false
482 | var metaBlock: [String] = []
483 | script.enumerateLines() { line, stop in
484 | let fullRange = NSRange(location: 0, length: line.count)
485 | if let _ = startRe.firstMatch(in: line, options: [], range: fullRange) {
486 | inMetaBlock = true
487 | } else if let _ = endRe.firstMatch(in: line, options: [], range: fullRange) {
488 | inMetaBlock = false
489 | } else if inMetaBlock {
490 | metaBlock.append(line)
491 | }
492 | }
493 |
494 | var metadata = ScriptMeta()
495 |
496 | for line in metaBlock {
497 | if !line.starts(with: "//") {
498 | return nil
499 | }
500 | var line = line
501 | line.removeSubrange(line.startIndex.. String {
21 | return url.absoluteString.addingPercentEncoding(withAllowedCharacters: .alphanumerics)!
22 | }
23 |
24 | func fileToURL(name: String) -> URL? {
25 | if let name = name.removingPercentEncoding {
26 | return URL(string: name)
27 | }
28 | return nil
29 | }
30 |
31 | public func getDependencyContents(of url: URL) -> String? {
32 | if let data = FileManager.default.contents(
33 | atPath: dependenciesURL.appendingPathComponent(urlToFile(url: url)).path) {
34 | if let string = String(data: data, encoding: .utf8) {
35 | return string
36 | }
37 | }
38 | return nil
39 | }
40 |
41 | var urlSession: URLSession?
42 |
43 | public func loadDependency(at url: URL, completion: @escaping (String?) -> Void) {
44 | if urlSession == nil {
45 | urlSession = URLSession(configuration: .ephemeral)
46 | }
47 |
48 | let task = urlSession!.dataTask(with: url) { (data, response, error) in
49 | if let data = data {
50 | FileManager.default.createFile(atPath: dependenciesURL.appendingPathComponent(self.urlToFile(url: url)).path, contents: data, attributes: nil)
51 | completion(String(data: data, encoding: .utf8))
52 | } else {
53 | completion(nil)
54 | }
55 | }
56 | task.resume()
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/PhageCoreTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/PhageCoreTests/PhageCoreTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhageCoreTests.swift
3 | // PhageCoreTests
4 | //
5 | // Created by cpsdqs on 2019-06-17.
6 | // Copyright © 2019 cpsdqs. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import PhageCore
11 |
12 | class PhageCoreTests: XCTestCase {
13 |
14 | override func setUp() {
15 | // Put setup code here. This method is called before the invocation of each test method in the class.
16 | }
17 |
18 | override func tearDown() {
19 | // Put teardown code here. This method is called after the invocation of each test method in the class.
20 | }
21 |
22 | func testCSSParsing() {
23 | let a = """
24 | hello { world { @document { this should be ignored } } }
25 |
26 | @document {
27 | this should not be ignored
28 | }
29 | #test {}
30 | @-moz-document url(http://example.com) {
31 | neither should this
32 | }
33 | @document url-prefix("test\\""), url('a') {
34 | nor this
35 | }
36 | """
37 | let ar = parseCSSFileSections(a)
38 | XCTAssertEqual(ar[0].rules.count, 0)
39 | XCTAssertEqual(ar[0].contents.trimmingCharacters(in: .whitespacesAndNewlines), "this should not be ignored")
40 | XCTAssertEqual(ar[1].rules.count, 1)
41 | XCTAssertEqual(ar[1].rules[0], .exact("http://example.com"))
42 | XCTAssertEqual(ar[1].contents.trimmingCharacters(in: .whitespacesAndNewlines), "neither should this")
43 | XCTAssertEqual(ar[2].rules.count, 2)
44 | XCTAssertEqual(ar[2].rules[0], .prefix("test\""))
45 | XCTAssertEqual(ar[2].rules[1], .exact("a"))
46 | XCTAssertEqual(ar[2].contents.trimmingCharacters(in: .whitespacesAndNewlines), "nor this")
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/PhageTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/PhageTests/PhageTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhageTests.swift
3 | // PhageTests
4 | //
5 | // Created by cpsdqs on 17.06.19.
6 | // Copyright © 2019 cpsdqs. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Phage
11 |
12 | class PhageTests: XCTestCase {
13 |
14 | override func setUp() {
15 | // Put setup code here. This method is called before the invocation of each test method in the class.
16 | }
17 |
18 | override func tearDown() {
19 | // Put teardown code here. This method is called after the invocation of each test method in the class.
20 | }
21 |
22 | func testExample() {
23 | // This is an example of a functional test case.
24 | // Use XCTAssert and related functions to verify your tests produce the correct results.
25 | }
26 |
27 | func testPerformanceExample() {
28 | // This is an example of a performance test case.
29 | self.measure {
30 | // Put the code you want to measure the time of here.
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/PhageUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/PhageUITests/PhageUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhageUITests.swift
3 | // PhageUITests
4 | //
5 | // Created by cpsdqs on 17.06.19.
6 | // Copyright © 2019 cpsdqs. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class PhageUITests: XCTestCase {
12 |
13 | override func setUp() {
14 | // Put setup code here. This method is called before the invocation of each test method in the class.
15 |
16 | // In UI tests it is usually best to stop immediately when a failure occurs.
17 | continueAfterFailure = false
18 |
19 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
20 | XCUIApplication().launch()
21 |
22 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
23 | }
24 |
25 | override func tearDown() {
26 | // Put teardown code here. This method is called after the invocation of each test method in the class.
27 | }
28 |
29 | func testExample() {
30 | // Use recording to get started writing UI tests.
31 | // Use XCTAssert and related functions to verify your tests produce the correct results.
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Phage
2 | A very simple Safari app extension that injects userscripts and userstyles into websites.
3 |
4 | Compared to e.g. Greasemonkey there are lots of features missing it’s very lightweight.
5 |
6 | 
7 |
8 | ## Short Guide
9 | Unlike most other userscript extensions, this extension will load your scripts and styles directly from the file system.
10 | Userscripts and userstyles are simply js and css files organized into bundles.
11 | Bundles are simply folders that are be placed inside the Phage `bundles` folder.
12 | To view this folder, click on “Show in Finder” in the Phage app.
13 |
14 | Userscripts should have a [Greasemonkey metadata block](https://wiki.greasespot.net/Metadata_Block).
15 | The following tags are supported:
16 |
17 | - `@match `: for matching URLs
18 | - `@require `: this tag will create a dependency, which can be loaded in the bundle’s detail view from the Phage app. (You can also find all loaded dependencies in the `dependencies` folder in the `bundles` folder’s parent directory)
19 |
20 | Currently, there is no Userscript API.
21 |
22 | Userstyles should place all style declarations inside a [`@document`](https://developer.mozilla.org/en-US/docs/Web/CSS/@document) declaration (`@-moz-document` will also work).
23 | Any rules outside will be ignored.
24 |
25 | ## Requirements
26 | - Xcode 11
27 | - macOS 10.15
28 |
29 | To build, simply open the project in Xcode, set your signing team in Signing & Capabilities, and run xcodebuild.
30 |
--------------------------------------------------------------------------------