diff --git a/package-lock.json b/package-lock.json index 5c79d23ad91..4af36f51a68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -74,7 +74,7 @@ "copy-webpack-plugin": "^5.1.1", "cordova-android": "^11.0.0", "cordova-browser": "~6.0.0", - "cordova-ios": "~6.3.0", + "cordova-ios": "github:apache/cordova-ios#1a5cd45e2243b239b5045a0ade9d2da1d779b72a", "cordova-lib": "^11.0.0", "cordova-osx": "github:apache/cordova-osx", "cordova-plugin-clipboard": "github:Jigsaw-Code/outline-cordova-plugin-clipboard#v2.0.0", @@ -7728,30 +7728,122 @@ } }, "node_modules/cordova-ios": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/cordova-ios/-/cordova-ios-6.3.0.tgz", - "integrity": "sha512-BZybgFzc7D0HmhkTYurFrRXiWgvYohmT7bwQsLPhf+VdiDjwGXbiSWgg3uP9MChvacCNcGXXUl2NhOaNC9UVaA==", + "version": "7.0.0-dev", + "resolved": "git+ssh://git@github.com/apache/cordova-ios.git#1a5cd45e2243b239b5045a0ade9d2da1d779b72a", + "integrity": "sha512-WKEu8ahzeLMby1ir8cb49dJKM4mgI1kXzGWjMldP0aILoRaPU/NENoxhJI5AQ6ozdXX4L3mD1+WkvncxLUNycw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "cordova-common": "^4.0.2", - "fs-extra": "^9.1.0", + "cordova-common": "^5.0.0", + "execa": "^5.1.1", + "fs-extra": "^11.1.1", "ios-sim": "^8.0.2", - "nopt": "^5.0.0", - "plist": "^3.0.1", - "semver": "^7.3.4", + "nopt": "^7.1.0", + "plist": "^3.0.6", + "semver": "^7.4.0", "unorm": "^1.6.0", - "which": "^2.0.2", + "which": "^3.0.0", "xcode": "^3.0.1", "xml-escape": "^1.1.0" }, "engines": { - "node": ">=10" + "node": ">=16.13.0" + } + }, + "node_modules/cordova-ios/node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/cordova-ios/node_modules/bplist-parser": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.2.tgz", + "integrity": "sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==", + "dev": true, + "dependencies": { + "big-integer": "1.6.x" + }, + "engines": { + "node": ">= 5.10.0" + } + }, + "node_modules/cordova-ios/node_modules/cordova-common": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cordova-common/-/cordova-common-5.0.0.tgz", + "integrity": "sha512-6Aa7o52/iEvsKx6K94ijzFel5acCULR49KL27OUVhEpJ4oS7Dc3y2eOP1Eu0P4Wmiw/eLEDQjGXGiAa2D5zFZA==", + "dev": true, + "dependencies": { + "@netflix/nerror": "^1.1.3", + "ansi": "^0.3.1", + "bplist-parser": "^0.3.2", + "cross-spawn": "^7.0.3", + "elementtree": "^0.1.7", + "endent": "^2.1.0", + "fast-glob": "^3.2.12", + "fs-extra": "^11.1.0", + "glob": "^7.1.6", + "lodash.assign": "^4.2.0", + "lodash.isdate": "^4.0.1", + "lodash.isobject": "^3.0.2", + "lodash.zip": "^4.2.0", + "plist": "^3.0.6", + "q": "^1.5.1", + "read-chunk": "^3.2.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/cordova-ios/node_modules/endent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/endent/-/endent-2.1.0.tgz", + "integrity": "sha512-r8VyPX7XL8U01Xgnb1CjZ3XV+z90cXIJ9JPE/R9SEC9vpw2P6CfsRPJmp20DppC5N7ZAMCmjYkJIa744Iyg96w==", + "dev": true, + "dependencies": { + "dedent": "^0.7.0", + "fast-json-parse": "^1.0.3", + "objectorarray": "^1.0.5" + } + }, + "node_modules/cordova-ios/node_modules/fs-extra": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/cordova-ios/node_modules/nopt": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.1.0.tgz", + "integrity": "sha512-ZFPLe9Iu0tnx7oWhFxAo4s7QTn8+NNDDxYNaKLjE7Dp0tbakQ3M1QhQzsnzXHQBTUO3K9BmwaxnyO8Ayn2I95Q==", + "dev": true, + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/cordova-ios/node_modules/semver": { - "version": "7.3.7", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", "dev": true, - "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" }, @@ -7762,6 +7854,21 @@ "node": ">=10" } }, + "node_modules/cordova-ios/node_modules/which": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.0.tgz", + "integrity": "sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/cordova-lib": { "version": "11.0.0", "dev": true, @@ -10362,9 +10469,10 @@ "license": "MIT" }, "node_modules/fast-glob": { - "version": "3.2.11", + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -13959,6 +14067,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha512-hFuH8TY+Yji7Eja3mGiuAxBqLagejScbG8GbG0j6o9vzn0YL14My+ktnqtZgFTosKymC9/44wP6s7xyuLfnClw==", + "dev": true + }, "node_modules/lodash.camelcase": { "version": "4.3.0", "dev": true, @@ -13973,10 +14087,22 @@ "version": "4.1.2", "license": "MIT" }, + "node_modules/lodash.isdate": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isdate/-/lodash.isdate-4.0.1.tgz", + "integrity": "sha512-hg5B1GD+R9egsBgMwmAhk+V53Us03TVvXT4dnyKugEfsD4QKuG9Wlyvxq8OGy2nu7qVGsh4DRSnMk33hoWBq/Q==", + "dev": true + }, "node_modules/lodash.isequal": { "version": "4.5.0", "license": "MIT" }, + "node_modules/lodash.isobject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==", + "dev": true + }, "node_modules/lodash.memoize": { "version": "4.1.2", "dev": true, @@ -13992,6 +14118,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.zip": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.zip/-/lodash.zip-4.2.0.tgz", + "integrity": "sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg==", + "dev": true + }, "node_modules/log4js": { "version": "6.6.1", "dev": true, @@ -26502,29 +26634,112 @@ } }, "cordova-ios": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/cordova-ios/-/cordova-ios-6.3.0.tgz", - "integrity": "sha512-BZybgFzc7D0HmhkTYurFrRXiWgvYohmT7bwQsLPhf+VdiDjwGXbiSWgg3uP9MChvacCNcGXXUl2NhOaNC9UVaA==", + "version": "git+ssh://git@github.com/apache/cordova-ios.git#1a5cd45e2243b239b5045a0ade9d2da1d779b72a", + "integrity": "sha512-WKEu8ahzeLMby1ir8cb49dJKM4mgI1kXzGWjMldP0aILoRaPU/NENoxhJI5AQ6ozdXX4L3mD1+WkvncxLUNycw==", "dev": true, + "from": "cordova-ios@github:apache/cordova-ios#1a5cd45e2243b239b5045a0ade9d2da1d779b72a", "requires": { - "cordova-common": "^4.0.2", - "fs-extra": "^9.1.0", + "cordova-common": "^5.0.0", + "execa": "^5.1.1", + "fs-extra": "^11.1.1", "ios-sim": "^8.0.2", - "nopt": "^5.0.0", - "plist": "^3.0.1", - "semver": "^7.3.4", + "nopt": "^7.1.0", + "plist": "^3.0.6", + "semver": "^7.4.0", "unorm": "^1.6.0", - "which": "^2.0.2", + "which": "^3.0.0", "xcode": "^3.0.1", "xml-escape": "^1.1.0" }, "dependencies": { + "abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true + }, + "bplist-parser": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.2.tgz", + "integrity": "sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==", + "dev": true, + "requires": { + "big-integer": "1.6.x" + } + }, + "cordova-common": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cordova-common/-/cordova-common-5.0.0.tgz", + "integrity": "sha512-6Aa7o52/iEvsKx6K94ijzFel5acCULR49KL27OUVhEpJ4oS7Dc3y2eOP1Eu0P4Wmiw/eLEDQjGXGiAa2D5zFZA==", + "dev": true, + "requires": { + "@netflix/nerror": "^1.1.3", + "ansi": "^0.3.1", + "bplist-parser": "^0.3.2", + "cross-spawn": "^7.0.3", + "elementtree": "^0.1.7", + "endent": "^2.1.0", + "fast-glob": "^3.2.12", + "fs-extra": "^11.1.0", + "glob": "^7.1.6", + "lodash.assign": "^4.2.0", + "lodash.isdate": "^4.0.1", + "lodash.isobject": "^3.0.2", + "lodash.zip": "^4.2.0", + "plist": "^3.0.6", + "q": "^1.5.1", + "read-chunk": "^3.2.0", + "strip-bom": "^4.0.0" + } + }, + "endent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/endent/-/endent-2.1.0.tgz", + "integrity": "sha512-r8VyPX7XL8U01Xgnb1CjZ3XV+z90cXIJ9JPE/R9SEC9vpw2P6CfsRPJmp20DppC5N7ZAMCmjYkJIa744Iyg96w==", + "dev": true, + "requires": { + "dedent": "^0.7.0", + "fast-json-parse": "^1.0.3", + "objectorarray": "^1.0.5" + } + }, + "fs-extra": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "nopt": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.1.0.tgz", + "integrity": "sha512-ZFPLe9Iu0tnx7oWhFxAo4s7QTn8+NNDDxYNaKLjE7Dp0tbakQ3M1QhQzsnzXHQBTUO3K9BmwaxnyO8Ayn2I95Q==", + "dev": true, + "requires": { + "abbrev": "^2.0.0" + } + }, "semver": { - "version": "7.3.7", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", "dev": true, "requires": { "lru-cache": "^6.0.0" } + }, + "which": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.0.tgz", + "integrity": "sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } } } }, @@ -28290,7 +28505,9 @@ "dev": true }, "fast-glob": { - "version": "3.2.11", + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", @@ -30681,6 +30898,12 @@ "version": "4.17.21", "dev": true }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha512-hFuH8TY+Yji7Eja3mGiuAxBqLagejScbG8GbG0j6o9vzn0YL14My+ktnqtZgFTosKymC9/44wP6s7xyuLfnClw==", + "dev": true + }, "lodash.camelcase": { "version": "4.3.0", "dev": true @@ -30692,9 +30915,21 @@ "lodash.escaperegexp": { "version": "4.1.2" }, + "lodash.isdate": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isdate/-/lodash.isdate-4.0.1.tgz", + "integrity": "sha512-hg5B1GD+R9egsBgMwmAhk+V53Us03TVvXT4dnyKugEfsD4QKuG9Wlyvxq8OGy2nu7qVGsh4DRSnMk33hoWBq/Q==", + "dev": true + }, "lodash.isequal": { "version": "4.5.0" }, + "lodash.isobject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==", + "dev": true + }, "lodash.memoize": { "version": "4.1.2", "dev": true @@ -30707,6 +30942,12 @@ "version": "4.5.0", "dev": true }, + "lodash.zip": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.zip/-/lodash.zip-4.2.0.tgz", + "integrity": "sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg==", + "dev": true + }, "log4js": { "version": "6.6.1", "dev": true, diff --git a/package.json b/package.json index d191e750278..700a3586273 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "copy-webpack-plugin": "^5.1.1", "cordova-android": "^11.0.0", "cordova-browser": "~6.0.0", - "cordova-ios": "~6.3.0", + "cordova-ios": "github:apache/cordova-ios#1a5cd45e2243b239b5045a0ade9d2da1d779b72a", "cordova-lib": "^11.0.0", "cordova-osx": "github:apache/cordova-osx", "cordova-plugin-clipboard": "github:Jigsaw-Code/outline-cordova-plugin-clipboard#v2.0.0", diff --git a/src/cordova/apple/OutlineAppleLib/.gitignore b/src/cordova/apple/OutlineAppleLib/.gitignore index 5922fdaa563..c4604397f38 100644 --- a/src/cordova/apple/OutlineAppleLib/.gitignore +++ b/src/cordova/apple/OutlineAppleLib/.gitignore @@ -2,6 +2,7 @@ /.build /Packages /*.xcodeproj +/*.xcframework xcuserdata/ DerivedData/ .swiftpm/config/registries.json diff --git a/src/cordova/apple/OutlineAppleLib/Package.swift b/src/cordova/apple/OutlineAppleLib/Package.swift index 966634ddba4..0219c673d4b 100644 --- a/src/cordova/apple/OutlineAppleLib/Package.swift +++ b/src/cordova/apple/OutlineAppleLib/Package.swift @@ -8,39 +8,71 @@ let package = Package( products: [ .library( name: "OutlineAppleLib", - targets: ["Tun2socks", "OutlineSentryLogger", "OutlineTunnel"]), + targets: ["Tun2socks", "OutlineSentryLogger", "OutlineTunnel", "OutlineCatalystApp", "OutlineNotification"] + ), + .library( + name: "OutlineLauncher", + targets: ["OutlineLauncher"] + ), + .library( + name: "OutlineAppKitBridge", + targets: ["OutlineAppKitBridge"] + ), .library( name: "PacketTunnelProvider", - targets: ["PacketTunnelProvider"]), + targets: ["PacketTunnelProvider"] + ), ], dependencies: [ .package(url: "https://github.com/CocoaLumberjack/CocoaLumberjack.git", from: "3.7.4"), .package(url: "https://github.com/getsentry/sentry-cocoa", from: "7.31.3"), ], targets: [ + .target( + name: "OutlineLauncher", + dependencies: + ["CocoaLumberjack", + .product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"), + "OutlineCatalystApp"] + ), + .target( + name: "OutlineCatalystApp", + dependencies: [ + "OutlineAppKitBridge", + .product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"), + ] + ), + .target( + name: "OutlineAppKitBridge", + dependencies: [ + .product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"), + "OutlineNotification", + ] + ), .target( name: "PacketTunnelProvider", dependencies: - ["CocoaLumberjack", - .product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"), - "Tun2socks", - "OutlineTunnel" - ], + ["CocoaLumberjack", + .product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"), + "Tun2socks", + "OutlineTunnel"], cSettings: [ .headerSearchPath("Internal"), ] ), + .target(name: "OutlineNotification"), .target( name: "OutlineSentryLogger", dependencies: [ .product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"), - .product(name: "Sentry", package: "sentry-cocoa") + .product(name: "Sentry", package: "sentry-cocoa"), ] ), .target( name: "OutlineTunnel", dependencies: [ .product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"), + "Tun2socks", ] ), .binaryTarget( @@ -50,6 +82,7 @@ let package = Package( ), .testTarget( name: "OutlineTunnelTest", - dependencies: ["OutlineTunnel", "PacketTunnelProvider"]), + dependencies: ["OutlineTunnel", "PacketTunnelProvider"] + ), ] ) diff --git a/src/cordova/apple/OutlineAppleLib/README.md b/src/cordova/apple/OutlineAppleLib/README.md index cca89c18fe6..51fb8089e7c 100644 --- a/src/cordova/apple/OutlineAppleLib/README.md +++ b/src/cordova/apple/OutlineAppleLib/README.md @@ -1,3 +1,4 @@ -# OutlineTun2Socks +# OutlineAppleLib -This package provides the Outline Tun2socks framework as a Swift Package. +This package provides all Outline Apple logic, including the Outline Tun2socks +framework, as a Swift Package. diff --git a/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/AppKitBridge.swift b/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/AppKitBridge.swift new file mode 100644 index 00000000000..9067e14b040 --- /dev/null +++ b/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/AppKitBridge.swift @@ -0,0 +1,83 @@ +// Copyright 2023 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#if os(macOS) + import AppKit + import CocoaLumberjackSwift + import ServiceManagement + + public class AppKitBridge: NSObject, AppKitBridgeProtocol { + private var statusItemController: StatusItemController? + static let kAppGroup = "QT8Z3Q9V3A.org.outline.macos.client" + static let kAppLauncherName = "launcher3" + + override public required init() { + super.init() + } + + /// Terminates the application. + @objc public func terminate() { + NSApp.terminate(self) + } + + /// Set the connection status in the app's menu in the system-wide menu bar. + @objc public func setConnectionStatus(_ status: ConnectionStatus) { + if statusItemController == nil { + DDLogInfo("[AppKitBridge] No status item controller found. Creating one now.") + statusItemController = StatusItemController() + } + statusItemController!.setStatus(status: status) + } + + /// Enables or disables the embedded app launcher as a login item. + @objc public func setAppLauncherEnabled(_ isEnabled: Bool) { + guard let launcherBundleId = getLauncherBundleId() else { + return DDLogError("[AppKitBridge] Unable to set launcher for missing bundle ID.") + } + + if !SMLoginItemSetEnabled(launcherBundleId as! CFString, isEnabled) { + return DDLogError("[AppKitBridge] Failed to set enable=\(isEnabled) for launcher \(launcherBundleId).") + } + + return DDLogInfo("[AppKitBridge] Successfully set enable=\(isEnabled) for launcher \(launcherBundleId).") + } + + /// Loads the main application from a given launcher bundle. + @objc public func loadMainApp(_ launcherBundleId: String) { + // Retrieve the main app's bundle ID programmatically from the embedded launcher bundle ID. + let mainAppBundleId = getMainBundleId(launcherBundleId) + DDLogInfo("[AppKitBridge] Loading main app \(mainAppBundleId) from launcher \(launcherBundleId).") + + let descriptor = NSAppleEventDescriptor(string: launcherBundleId) + NSWorkspace.shared.launchApplication(withBundleIdentifier: mainAppBundleId, + options: [.withoutActivation, .andHide], + additionalEventParamDescriptor: descriptor, + launchIdentifier: nil) + } + } + + /// Returns the embedded launcher application's bundle ID. + private func getLauncherBundleId() -> String? { + guard let bundleId = Bundle.main.bundleIdentifier else { + DDLogError("[AppKitBridge] Failed to retrieve the application's bundle ID.") + return nil + } + return String(format: "%@.%@", bundleId, AppKitBridge.kAppLauncherName) + } + + /// Returns the main application's bundle ID from the embedded launcher bundle ID. + private func getMainBundleId(_ launcherBundleId: String) -> String { + return (launcherBundleId as NSString).deletingPathExtension + } +#endif diff --git a/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/AppKitBridgeProtocol.swift b/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/AppKitBridgeProtocol.swift new file mode 100644 index 00000000000..2df694d8cc0 --- /dev/null +++ b/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/AppKitBridgeProtocol.swift @@ -0,0 +1,28 @@ +// Copyright 2023 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +@objc +public protocol AppKitBridgeProtocol: NSObjectProtocol { + init() + + func terminate() + + func setConnectionStatus(_ status: ConnectionStatus) + + func setAppLauncherEnabled(_ isEnabled: Bool) + + func loadMainApp(_ launcherBundleId: String) +} diff --git a/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/ConnectionStatus.swift b/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/ConnectionStatus.swift new file mode 100644 index 00000000000..bf0171db456 --- /dev/null +++ b/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/ConnectionStatus.swift @@ -0,0 +1,19 @@ +// Copyright 2023 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@objc public enum ConnectionStatus: Int { + case unknown + case connected + case disconnected +} diff --git a/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/Resources/Assets.xcassets/Contents.json b/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/Resources/Assets.xcassets/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/Resources/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/Resources/Assets.xcassets/status_bar_button_image.imageset/Contents.json b/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/Resources/Assets.xcassets/status_bar_button_image.imageset/Contents.json new file mode 100644 index 00000000000..27d38b248ed --- /dev/null +++ b/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/Resources/Assets.xcassets/status_bar_button_image.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "outline-black-off-2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/Resources/Assets.xcassets/status_bar_button_image.imageset/outline-black-off-2x.png b/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/Resources/Assets.xcassets/status_bar_button_image.imageset/outline-black-off-2x.png new file mode 100644 index 00000000000..4785b8a26f5 Binary files /dev/null and b/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/Resources/Assets.xcassets/status_bar_button_image.imageset/outline-black-off-2x.png differ diff --git a/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/Resources/Assets.xcassets/status_bar_button_image_connected.imageset/Contents.json b/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/Resources/Assets.xcassets/status_bar_button_image_connected.imageset/Contents.json new file mode 100644 index 00000000000..c915e5cb0aa --- /dev/null +++ b/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/Resources/Assets.xcassets/status_bar_button_image_connected.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "outline-black-on-2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/Resources/Assets.xcassets/status_bar_button_image_connected.imageset/outline-black-on-2x.png b/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/Resources/Assets.xcassets/status_bar_button_image_connected.imageset/outline-black-on-2x.png new file mode 100644 index 00000000000..6c1ceb12b81 Binary files /dev/null and b/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/Resources/Assets.xcassets/status_bar_button_image_connected.imageset/outline-black-on-2x.png differ diff --git a/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/StatusItemController.swift b/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/StatusItemController.swift new file mode 100644 index 00000000000..90308f96af0 --- /dev/null +++ b/src/cordova/apple/OutlineAppleLib/Sources/OutlineAppKitBridge/StatusItemController.swift @@ -0,0 +1,101 @@ +// Copyright 2023 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#if os(macOS) + import AppKit + import CocoaLumberjackSwift + import OutlineNotification + + var StatusItem = NSStatusItem() + + class StatusItemController: NSObject { + let connectionStatusMenuItem = NSMenuItem(title: MenuTitle.statusDisconnected, + action: nil, + keyEquivalent: "") + + private enum AppIconImage { + static let statusConnected = getImage(name: "status_bar_button_image_connected") + static let statusDisconnected = getImage(name: "status_bar_button_image") + } + + // TODO: Internationalize these user-facing strings. + private enum MenuTitle { + static let open = "Open" + static let quit = "Quit" + static let statusConnected = "Connected" + static let statusDisconnected = "Disconnected" + } + + override init() { + super.init() + + DDLogInfo("[StatusItemController] Creating status menu") + StatusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength) + setStatus(status: .disconnected) + + let menu = NSMenu() + let openMenuItem = NSMenuItem(title: MenuTitle.open, action: #selector(openApplication), keyEquivalent: "o") + openMenuItem.target = self + menu.addItem(openMenuItem) + menu.addItem(connectionStatusMenuItem) + menu.addItem(NSMenuItem.separator()) + let closeMenuItem = NSMenuItem(title: MenuTitle.quit, action: #selector(closeApplication), keyEquivalent: "q") + closeMenuItem.target = self + menu.addItem(closeMenuItem) + StatusItem.menu = menu + } + + func setStatus(status: ConnectionStatus) { + let isConnected = status == .connected + let appIconImage = isConnected ? AppIconImage.statusConnected : AppIconImage.statusDisconnected + appIconImage.isTemplate = true + StatusItem.button?.image = appIconImage + + let connectionStatusTitle = isConnected ? MenuTitle.statusConnected : MenuTitle.statusDisconnected + connectionStatusMenuItem.title = connectionStatusTitle + } + + @objc func openApplication(_: AnyObject?) { + DDLogInfo("[StatusItemController] Opening application") + NSApp.activate(ignoringOtherApps: true) + guard let uiWindow = getUiWindow() else { + return + } + uiWindow.makeKeyAndOrderFront(self) + } + + @objc func closeApplication(_: AnyObject?) { + DDLogInfo("[StatusItemController] Closing application") + NotificationCenter.default.post(name: .kAppQuit, object: nil) + NSApplication.shared.terminate(self) + } + } + + private func getUiWindow() -> NSWindow? { + for window in NSApp.windows { + if String(describing: window).contains("UINSWindow") { + return window + } + } + return nil + } + + private func getImage(name: String) -> NSImage { + guard let image = Bundle.module.image(forResource: NSImage.Name(name)) else { + fatalError("Unable to load image asset named \(name).") + } + return image + } + +#endif diff --git a/src/cordova/apple/OutlineAppleLib/Sources/OutlineCatalystApp/AppKitBundleLoader.swift b/src/cordova/apple/OutlineAppleLib/Sources/OutlineCatalystApp/AppKitBundleLoader.swift new file mode 100644 index 00000000000..a98024d9e9b --- /dev/null +++ b/src/cordova/apple/OutlineAppleLib/Sources/OutlineCatalystApp/AppKitBundleLoader.swift @@ -0,0 +1,37 @@ +// Copyright 2023 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import CocoaLumberjackSwift +import Foundation +import OutlineAppKitBridge + +enum BridgeBundle { + static let fileName = "AppKitBridge.bundle" + static let className = "OutlineAppKitBridge.AppKitBridge" +} + +public func createAppKitBridge() -> AppKitBridgeProtocol { + guard let bundleURL = Bundle.main.builtInPlugInsURL?.appendingPathComponent(BridgeBundle.fileName) else { + preconditionFailure("[AppKitBundleLoader] \(BridgeBundle.fileName) should exist") + } + guard let bundle = Bundle(url: bundleURL) else { + preconditionFailure("[AppKitBundleLoader] \(BridgeBundle.fileName) should exist") + } + DDLogInfo("[AppKitBundleLoader] AppKit bundle loaded successfully") + let className = BridgeBundle.className + guard let appKitBridgeClass = bundle.classNamed(className) as? AppKitBridgeProtocol.Type else { + preconditionFailure("[AppKitBundleLoader] Cannot initialise \(className) from \(BridgeBundle.fileName)") + } + return appKitBridgeClass.init() +} diff --git a/src/cordova/apple/OutlineAppleLib/Sources/OutlineCatalystApp/OutlineCatalystApp.swift b/src/cordova/apple/OutlineAppleLib/Sources/OutlineCatalystApp/OutlineCatalystApp.swift new file mode 100644 index 00000000000..a9624eb190d --- /dev/null +++ b/src/cordova/apple/OutlineAppleLib/Sources/OutlineCatalystApp/OutlineCatalystApp.swift @@ -0,0 +1,61 @@ +// Copyright 2023 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#if targetEnvironment(macCatalyst) + import CocoaLumberjack + import CocoaLumberjackSwift + import Foundation + import OutlineAppKitBridge + import OutlineNotification + import ServiceManagement + + @objcMembers + public class OutlineCatalystApp: NSObject { + public static func initApp() { + DDLog.add(DDOSLogger.sharedInstance) + + let appKitBridge: AppKitBridgeProtocol = createAppKitBridge() + + // Configure the window. + let scenes = UIApplication.shared.connectedScenes + for scene in scenes { + let windowScene = (scene as! UIWindowScene) + windowScene.titlebar?.titleVisibility = .hidden + windowScene.titlebar?.toolbar = nil + windowScene.sizeRestrictions?.minimumSize = CGSizeMake(400, 550) + windowScene.sizeRestrictions?.maximumSize = CGSizeMake(400, 550) + } + + // Initiate the connection status menu in unknown state by default. + // TODO: Check status in case the the VPN is already running. + appKitBridge.setConnectionStatus(.unknown) + + NotificationCenter.default.addObserver(forName: NSNotification.kVpnConnected, + object: nil, + queue: nil) + { _ in + appKitBridge.setConnectionStatus(.connected) + } + NotificationCenter.default.addObserver(forName: NSNotification.kVpnDisconnected, + object: nil, + queue: nil) + { _ in + appKitBridge.setConnectionStatus(.disconnected) + } + + // Enable app launcher to start on boot. + appKitBridge.setAppLauncherEnabled(true) + } + } +#endif diff --git a/src/cordova/apple/OutlineAppleLib/Sources/OutlineLauncher/AppDelegate.swift b/src/cordova/apple/OutlineAppleLib/Sources/OutlineLauncher/AppDelegate.swift new file mode 100644 index 00000000000..06aafc23b2c --- /dev/null +++ b/src/cordova/apple/OutlineAppleLib/Sources/OutlineLauncher/AppDelegate.swift @@ -0,0 +1,64 @@ +// Copyright 2018 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#if targetEnvironment(macCatalyst) + import CocoaLumberjack + import CocoaLumberjackSwift + import NetworkExtension + import OutlineCatalystApp + import UIKit + + @UIApplicationMain + class AppDelegate: UIResponder, UIApplicationDelegate { + func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + DDLog.add(DDOSLogger.sharedInstance) + + let appKitBridge = createAppKitBridge() + shouldLaunchMainApp { shouldLaunch in + defer { + DDLogInfo("Exiting launcher...") + appKitBridge.terminate() + } + if !shouldLaunch { + DDLogInfo("Not launching, Outline not connected at shutdown") + return + } + DDLogInfo("Outline connected at shutdown. Launching") + + guard let launcherBundleId = Bundle.main.bundleIdentifier else { + DDLogError("Failed to retrieve the bundle ID for the launcher app.") + return + } + appKitBridge.loadMainApp(launcherBundleId) + } + return true + } + + // Returns whether the launcher should launch the main app. + private func shouldLaunchMainApp(completion: @escaping (Bool) -> Void) { + NETunnelProviderManager.loadAllFromPreferences { managers, error in + guard error == nil, managers != nil else { + DDLogError("Failed to get tunnel manager: \(String(describing: error))") + return completion(false) + } + guard let manager: NETunnelProviderManager = managers!.first, managers!.count > 0 else { + DDLogError("No tunnel managers found.") + return completion(false) + } + DDLogInfo("Tunnel manager found.") + return completion(manager.isOnDemandEnabled) + } + } + } +#endif diff --git a/src/cordova/apple/OutlineAppleLib/Sources/OutlineNotification/NSNotification+Outline.swift b/src/cordova/apple/OutlineAppleLib/Sources/OutlineNotification/NSNotification+Outline.swift new file mode 100644 index 00000000000..72e168e2770 --- /dev/null +++ b/src/cordova/apple/OutlineAppleLib/Sources/OutlineNotification/NSNotification+Outline.swift @@ -0,0 +1,30 @@ +// Copyright 2023 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +// TODO: Move this to a better location and clean up where notifications get +// sent and consumed. + +public extension Notification.Name { + static let kAppQuit = Notification.Name("appQuit") + static let kVpnConnected = Notification.Name("vpnConnected") + static let kVpnDisconnected = Notification.Name("vpnDisconnected") +} + +@objc public extension NSNotification { + static let kAppQuit = Notification.Name.kAppQuit + static let kVpnConnected = Notification.Name.kVpnConnected + static let kVpnDisconnected = Notification.Name.kVpnDisconnected +} diff --git a/src/cordova/apple/OutlineAppleLib/Sources/PacketTunnelProvider/PacketTunnelProvider.m b/src/cordova/apple/OutlineAppleLib/Sources/PacketTunnelProvider/PacketTunnelProvider.m index 621196ed239..8dea141f95c 100644 --- a/src/cordova/apple/OutlineAppleLib/Sources/PacketTunnelProvider/PacketTunnelProvider.m +++ b/src/cordova/apple/OutlineAppleLib/Sources/PacketTunnelProvider/PacketTunnelProvider.m @@ -49,10 +49,10 @@ @implementation PacketTunnelProvider - (id)init { self = [super init]; -#if TARGET_OS_IPHONE - NSString *appGroup = @"group.org.outline.ios.client"; -#else +#if (TARGET_OS_OSX || TARGET_OS_MACCATALYST) NSString *appGroup = @"QT8Z3Q9V3A.org.outline.macos.client"; +#else + NSString *appGroup = @"group.org.outline.ios.client"; #endif NSURL *containerUrl = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:appGroup]; diff --git a/src/cordova/apple/xcode/ios/Outline.xcodeproj/project.pbxproj b/src/cordova/apple/xcode/ios/Outline.xcodeproj/project.pbxproj index 1a0c2cc6e86..429ab77cf84 100755 --- a/src/cordova/apple/xcode/ios/Outline.xcodeproj/project.pbxproj +++ b/src/cordova/apple/xcode/ios/Outline.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 0207DA581B56EA530066E2B4 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0207DA571B56EA530066E2B4 /* Images.xcassets */; }; + 0207DA581B56EA530066E2B4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0207DA571B56EA530066E2B4 /* Assets.xcassets */; }; 1273B4E700B84E31B2528701 /* CDVClipboard.m in Sources */ = {isa = PBXBuildFile; fileRef = 91E45572BB494E9299D2DD41 /* CDVClipboard.m */; }; 1CE7466BA73B4CCB838EBE21 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 941052A220F54953928FF2E2 /* libz.tbd */; }; 1D3623260D0F684500981E51 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D3623250D0F684500981E51 /* AppDelegate.m */; }; @@ -16,14 +16,22 @@ 301BF552109A68D80062928A /* libCordova.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF535109A57CC0062928A /* libCordova.a */; }; 302D95F114D2391D003F00A1 /* MainViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 302D95EF14D2391D003F00A1 /* MainViewController.m */; }; 302D95F214D2391D003F00A1 /* MainViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 302D95F014D2391D003F00A1 /* MainViewController.xib */; }; - 3B0347531F212F0200C8EF1F /* VpnExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 3B0347481F212F0100C8EF1F /* VpnExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 52CBB892295BD8F200D0073F /* CocoaLumberjack in Frameworks */ = {isa = PBXBuildFile; productRef = 52CBB891295BD8F200D0073F /* CocoaLumberjack */; }; - 52CBB894295BD8F200D0073F /* CocoaLumberjackSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 52CBB893295BD8F200D0073F /* CocoaLumberjackSwift */; }; + 3B0347531F212F0200C8EF1F /* VpnExtension.appex in Embed App Extensions (2 items) */ = {isa = PBXBuildFile; fileRef = 3B0347481F212F0100C8EF1F /* VpnExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 52CE53E7295B6A310064D03D /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 52CE53E6295B6A310064D03D /* Sentry */; }; 52E783062A5880CF00355E64 /* PacketTunnelProvider in Frameworks */ = {isa = PBXBuildFile; productRef = 52E783052A5880CF00355E64 /* PacketTunnelProvider */; }; 5F7F90AE0E924FD7B065C415 /* CDVStatusBar.m in Sources */ = {isa = PBXBuildFile; fileRef = 0394302BA6114B2AB648D4FF /* CDVStatusBar.m */; }; 6AFF5BF91D6E424B00AB3073 /* CDVLaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6AFF5BF81D6E424B00AB3073 /* CDVLaunchScreen.storyboard */; }; - F63DC2182970AFFA00D92E0A /* OutlineAppleLib in Frameworks */ = {isa = PBXBuildFile; productRef = F63DC2172970AFFA00D92E0A /* OutlineAppleLib */; }; + A271D4202A7069D8009981B2 /* OutlineLauncher in Frameworks */ = {isa = PBXBuildFile; productRef = A271D41F2A7069D8009981B2 /* OutlineLauncher */; }; + A271D4222A706CB9009981B2 /* OutlineAppKitBridge in Frameworks */ = {isa = PBXBuildFile; productRef = A271D4212A706CB9009981B2 /* OutlineAppKitBridge */; }; + A271D42D2A708240009981B2 /* AppDelegate+Outline.m in Sources */ = {isa = PBXBuildFile; fileRef = A271D42C2A708240009981B2 /* AppDelegate+Outline.m */; }; + A271D4302A708278009981B2 /* CocoaLumberjack in Frameworks */ = {isa = PBXBuildFile; productRef = A271D42F2A708278009981B2 /* CocoaLumberjack */; }; + A271D4322A708278009981B2 /* CocoaLumberjackSwift in Frameworks */ = {isa = PBXBuildFile; productRef = A271D4312A708278009981B2 /* CocoaLumberjackSwift */; }; + A271D4342A70829D009981B2 /* OutlineAppleLib in Frameworks */ = {isa = PBXBuildFile; productRef = A271D4332A70829D009981B2 /* OutlineAppleLib */; }; + A27F43AA29F6EC43002C3678 /* config.xml in CopyFiles */ = {isa = PBXBuildFile; fileRef = F840E1F0165FE0F500CFE078 /* config.xml */; }; + A27F43AB29F6EC43002C3678 /* www in CopyFiles */ = {isa = PBXBuildFile; fileRef = 301BF56E109A69640062928A /* www */; }; + A2A83B392A1C5B2E00755F56 /* OutlineLauncher.app in Copy OutlineLauncher */ = {isa = PBXBuildFile; fileRef = A26D262D2A1C41B1009838E0 /* OutlineLauncher.app */; platformFilter = maccatalyst; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + A2B8C14D2A58B70E0054487E /* AppKitBridge.bundle in Embed PlugIns */ = {isa = PBXBuildFile; fileRef = A2DBB2F62A00D2DD0017E696 /* AppKitBridge.bundle */; platformFilter = maccatalyst; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + A2DBB2FB2A00D2F90017E696 /* AppKitBridge.bundle in Embed App Extensions (2 items) */ = {isa = PBXBuildFile; fileRef = A2DBB2F62A00D2DD0017E696 /* AppKitBridge.bundle */; platformFilter = maccatalyst; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; FC8C310B1FAA814A004262BE /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC8C310A1FAA814A004262BE /* NetworkExtension.framework */; }; FC8C310C1FAA88FB004262BE /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC8C310A1FAA814A004262BE /* NetworkExtension.framework */; }; /* End PBXBuildFile section */ @@ -57,35 +65,80 @@ remoteGlobalIDString = C0C01EB21E3911D50056E6CB; remoteInfo = Cordova; }; + A2B8C14E2A58B70E0054487E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; + proxyType = 1; + remoteGlobalIDString = A2DBB2F52A00D2DD0017E696; + remoteInfo = AppKitBridge; + }; + A2DBB3032A00D5F90017E696 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; + proxyType = 1; + remoteGlobalIDString = A2DBB2F52A00D2DD0017E696; + remoteInfo = AppKitBridge; + }; + A2F6068D2A1C6D4A0039183A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; + proxyType = 1; + remoteGlobalIDString = A26D262C2A1C41B1009838E0; + remoteInfo = OutlineLauncher; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ - 3B0347571F212F0200C8EF1F /* Embed App Extensions */ = { + 3B0347571F212F0200C8EF1F /* Embed App Extensions (2 items) */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 13; files = ( - 3B0347531F212F0200C8EF1F /* VpnExtension.appex in Embed App Extensions */, + A2DBB2FB2A00D2F90017E696 /* AppKitBridge.bundle in Embed App Extensions (2 items) */, + 3B0347531F212F0200C8EF1F /* VpnExtension.appex in Embed App Extensions (2 items) */, + ); + name = "Embed App Extensions (2 items)"; + runOnlyForDeploymentPostprocessing = 0; + }; + A27F43A929F6EC1C002C3678 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 7; + files = ( + A27F43AA29F6EC43002C3678 /* config.xml in CopyFiles */, + A27F43AB29F6EC43002C3678 /* www in CopyFiles */, ); - name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; }; - 3B0347691F21321500C8EF1F /* Embed Frameworks */ = { + A2A83B382A1C5B0C00755F56 /* Copy OutlineLauncher */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 12; + dstPath = Contents/Library/LoginItems; + dstSubfolderSpec = 1; + files = ( + A2A83B392A1C5B2E00755F56 /* OutlineLauncher.app in Copy OutlineLauncher */, + ); + name = "Copy OutlineLauncher"; + runOnlyForDeploymentPostprocessing = 0; + }; + A2B8C1502A58B70E0054487E /* Embed PlugIns */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; - dstSubfolderSpec = 10; + dstSubfolderSpec = 13; files = ( + A2B8C14D2A58B70E0054487E /* AppKitBridge.bundle in Embed PlugIns */, ); - name = "Embed Frameworks"; + name = "Embed PlugIns"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 0168F53D3AFF46F5B346C874 /* CDVStatusBar.h */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.c.h; name = CDVStatusBar.h; path = "cordova-plugin-statusbar/CDVStatusBar.h"; sourceTree = ""; }; - 0207DA571B56EA530066E2B4 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Outline/Images.xcassets; sourceTree = SOURCE_ROOT; }; + 0207DA571B56EA530066E2B4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Outline/Assets.xcassets; sourceTree = SOURCE_ROOT; }; 0394302BA6114B2AB648D4FF /* CDVStatusBar.m */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.c.objc; name = CDVStatusBar.m; path = "cordova-plugin-statusbar/CDVStatusBar.m"; sourceTree = ""; }; 1D3623240D0F684500981E51 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 1D3623250D0F684500981E51 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -108,6 +161,11 @@ 91E45572BB494E9299D2DD41 /* CDVClipboard.m */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.c.objc; name = CDVClipboard.m; path = "cordova-plugin-clipboard/CDVClipboard.m"; sourceTree = ""; }; 936C2951B7544BC8A20B6746 /* CDVClipboard.h */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.c.h; name = CDVClipboard.h; path = "cordova-plugin-clipboard/CDVClipboard.h"; sourceTree = ""; }; 941052A220F54953928FF2E2 /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; + A26D262D2A1C41B1009838E0 /* OutlineLauncher.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OutlineLauncher.app; sourceTree = BUILT_PRODUCTS_DIR; }; + A26D26382A1C41B4009838E0 /* OutlineLauncher.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = OutlineLauncher.entitlements; sourceTree = ""; }; + A271D42C2A708240009981B2 /* AppDelegate+Outline.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "AppDelegate+Outline.m"; sourceTree = ""; }; + A271D42E2A708253009981B2 /* AppDelegate+Outline.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AppDelegate+Outline.h"; sourceTree = ""; }; + A2DBB2F62A00D2DD0017E696 /* AppKitBridge.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppKitBridge.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; AAFAFA54943F490EAF4CD5BC /* OutlinePlugin.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = OutlinePlugin.swift; path = "cordova-plugin-outline/OutlinePlugin.swift"; sourceTree = ""; }; EB87FDF31871DA8E0020F90C /* www */ = {isa = PBXFileReference; lastKnownFileType = folder; name = www; path = ../../www; sourceTree = ""; }; EB87FDF41871DAF40020F90C /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = config.xml; path = ../../config.xml; sourceTree = ""; }; @@ -123,12 +181,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A271D4302A708278009981B2 /* CocoaLumberjack in Frameworks */, 301BF552109A68D80062928A /* libCordova.a in Frameworks */, FC8C310C1FAA88FB004262BE /* NetworkExtension.framework in Frameworks */, - 52CBB894295BD8F200D0073F /* CocoaLumberjackSwift in Frameworks */, + A271D4322A708278009981B2 /* CocoaLumberjackSwift in Frameworks */, + A271D4342A70829D009981B2 /* OutlineAppleLib in Frameworks */, 52CE53E7295B6A310064D03D /* Sentry in Frameworks */, - F63DC2182970AFFA00D92E0A /* OutlineAppleLib in Frameworks */, - 52CBB892295BD8F200D0073F /* CocoaLumberjack in Frameworks */, 1CE7466BA73B4CCB838EBE21 /* libz.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -142,6 +200,22 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + A26D262A2A1C41B1009838E0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A271D4202A7069D8009981B2 /* OutlineLauncher in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2DBB2F32A00D2DD0017E696 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A271D4222A706CB9009981B2 /* OutlineAppKitBridge in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -153,6 +227,8 @@ 302D95F014D2391D003F00A1 /* MainViewController.xib */, 1D3623240D0F684500981E51 /* AppDelegate.h */, 1D3623250D0F684500981E51 /* AppDelegate.m */, + A271D42C2A708240009981B2 /* AppDelegate+Outline.m */, + A271D42E2A708253009981B2 /* AppDelegate+Outline.h */, ); name = Classes; path = Outline/Classes; @@ -163,6 +239,8 @@ children = ( 1D6058910D05DD3D006BFB54 /* Outline.app */, 3B0347481F212F0100C8EF1F /* VpnExtension.appex */, + A2DBB2F62A00D2DD0017E696 /* AppKitBridge.bundle */, + A26D262D2A1C41B1009838E0 /* OutlineLauncher.app */, ); name = Products; sourceTree = ""; @@ -181,6 +259,7 @@ 29B97315FDCFA39411CA2CEA /* Other Sources */, 29B97317FDCFA39411CA2CEA /* Resources */, 3B0347491F212F0200C8EF1F /* VpnExtension */, + A26D262E2A1C41B1009838E0 /* OutlineLauncher */, 29B97323FDCFA39411CA2CEA /* Frameworks */, 19C28FACFE9D520D11CA2CBB /* Products */, ); @@ -202,7 +281,7 @@ isa = PBXGroup; children = ( FC55AB411F4F960A0056F12C /* VpnExtension-Info.plist */, - 0207DA571B56EA530066E2B4 /* Images.xcassets */, + 0207DA571B56EA530066E2B4 /* Assets.xcassets */, 3047A50E1AB8057F00498E2A /* config */, 8D1107310486CEB800E47090 /* Outline-Info.plist */, 6AFF5BF81D6E424B00AB3073 /* CDVLaunchScreen.storyboard */, @@ -261,6 +340,14 @@ path = Outline/Resources/vpn; sourceTree = ""; }; + A26D262E2A1C41B1009838E0 /* OutlineLauncher */ = { + isa = PBXGroup; + children = ( + A26D26382A1C41B4009838E0 /* OutlineLauncher.entitlements */, + ); + path = OutlineLauncher; + sourceTree = ""; + }; EB87FDF11871DA420020F90C /* Staging */ = { isa = PBXGroup; children = ( @@ -285,26 +372,28 @@ isa = PBXNativeTarget; buildConfigurationList = 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "Outline" */; buildPhases = ( - 304B58A110DAC018002A0835 /* Copy www directory */, + A27F43A929F6EC1C002C3678 /* CopyFiles */, 1D60588D0D05DD3D006BFB54 /* Resources */, 1D60588E0D05DD3D006BFB54 /* Sources */, 1D60588F0D05DD3D006BFB54 /* Frameworks */, - 3B0347571F212F0200C8EF1F /* Embed App Extensions */, - 3B0347691F21321500C8EF1F /* Embed Frameworks */, + A2A83B382A1C5B0C00755F56 /* Copy OutlineLauncher */, + 3B0347571F212F0200C8EF1F /* Embed App Extensions (2 items) */, FC0FFD6C1FCCE21E00EB0129 /* Remove unused framework architectures */, ); buildRules = ( ); dependencies = ( + A2F6068E2A1C6D4A0039183A /* PBXTargetDependency */, + A2DBB3042A00D5F90017E696 /* PBXTargetDependency */, 301BF551109A68C00062928A /* PBXTargetDependency */, 3B0347521F212F0200C8EF1F /* PBXTargetDependency */, ); name = Outline; packageProductDependencies = ( 52CE53E6295B6A310064D03D /* Sentry */, - 52CBB891295BD8F200D0073F /* CocoaLumberjack */, - 52CBB893295BD8F200D0073F /* CocoaLumberjackSwift */, - F63DC2172970AFFA00D92E0A /* OutlineAppleLib */, + A271D42F2A708278009981B2 /* CocoaLumberjack */, + A271D4312A708278009981B2 /* CocoaLumberjackSwift */, + A271D4332A70829D009981B2 /* OutlineAppleLib */, ); productName = Outline; productReference = 1D6058910D05DD3D006BFB54 /* Outline.app */; @@ -316,7 +405,6 @@ buildPhases = ( 3B0347441F212F0100C8EF1F /* Sources */, 3B0347451F212F0100C8EF1F /* Frameworks */, - 3B0347461F212F0100C8EF1F /* Resources */, ); buildRules = ( ); @@ -330,12 +418,53 @@ productReference = 3B0347481F212F0100C8EF1F /* VpnExtension.appex */; productType = "com.apple.product-type.app-extension"; }; + A26D262C2A1C41B1009838E0 /* OutlineLauncher */ = { + isa = PBXNativeTarget; + buildConfigurationList = A26D26392A1C41B4009838E0 /* Build configuration list for PBXNativeTarget "OutlineLauncher" */; + buildPhases = ( + A25EB8812A8437D700B92EE0 /* Sources */, + A26D262A2A1C41B1009838E0 /* Frameworks */, + A2B8C1502A58B70E0054487E /* Embed PlugIns */, + ); + buildRules = ( + ); + dependencies = ( + A2B8C14F2A58B70E0054487E /* PBXTargetDependency */, + ); + name = OutlineLauncher; + packageProductDependencies = ( + A271D41F2A7069D8009981B2 /* OutlineLauncher */, + ); + productName = OutlineLauncher; + productReference = A26D262D2A1C41B1009838E0 /* OutlineLauncher.app */; + productType = "com.apple.product-type.application"; + }; + A2DBB2F52A00D2DD0017E696 /* AppKitBridge */ = { + isa = PBXNativeTarget; + buildConfigurationList = A2DBB2F72A00D2DD0017E696 /* Build configuration list for PBXNativeTarget "AppKitBridge" */; + buildPhases = ( + A2DBB2F22A00D2DD0017E696 /* Sources */, + A2DBB2F32A00D2DD0017E696 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AppKitBridge; + packageProductDependencies = ( + A271D4212A706CB9009981B2 /* OutlineAppKitBridge */, + ); + productName = AppKitBridge; + productReference = A2DBB2F62A00D2DD0017E696 /* AppKitBridge.bundle */; + productType = "com.apple.product-type.bundle"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 29B97313FDCFA39411CA2CEA /* Project object */ = { isa = PBXProject; attributes = { + LastSwiftUpdateCheck = 1430; LastUpgradeCheck = 1010; TargetAttributes = { 1D6058900D05DD3D006BFB54 = { @@ -355,6 +484,13 @@ }; }; }; + A26D262C2A1C41B1009838E0 = { + CreatedOnToolsVersion = 14.3; + }; + A2DBB2F52A00D2DD0017E696 = { + CreatedOnToolsVersion = 14.3; + LastSwiftMigration = 1430; + }; }; }; buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Outline" */; @@ -382,6 +518,8 @@ targets = ( 1D6058900D05DD3D006BFB54 /* Outline */, 3B0347471F212F0100C8EF1F /* VpnExtension */, + A2DBB2F52A00D2DD0017E696 /* AppKitBridge */, + A26D262C2A1C41B1009838E0 /* OutlineLauncher */, ); }; /* End PBXProject section */ @@ -409,36 +547,14 @@ buildActionMask = 2147483647; files = ( 302D95F214D2391D003F00A1 /* MainViewController.xib in Resources */, - 0207DA581B56EA530066E2B4 /* Images.xcassets in Resources */, + 0207DA581B56EA530066E2B4 /* Assets.xcassets in Resources */, 6AFF5BF91D6E424B00AB3073 /* CDVLaunchScreen.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; - 3B0347461F212F0100C8EF1F /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 304B58A110DAC018002A0835 /* Copy www directory */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Copy www directory"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"$SRCROOT/Outline/Scripts/copy-www-build-step.sh\"\n"; - showEnvVarsInLog = 0; - }; FC0FFD6C1FCCE21E00EB0129 /* Remove unused framework architectures */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -466,6 +582,7 @@ 5F7F90AE0E924FD7B065C415 /* CDVStatusBar.m in Sources */, 2A617D29B96942E58B082FAC /* OutlinePlugin.swift in Sources */, 1273B4E700B84E31B2528701 /* CDVClipboard.m in Sources */, + A271D42D2A708240009981B2 /* AppDelegate+Outline.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -476,6 +593,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + A25EB8812A8437D700B92EE0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2DBB2F22A00D2DD0017E696 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -489,6 +620,24 @@ target = 3B0347471F212F0100C8EF1F /* VpnExtension */; targetProxy = 3B0347511F212F0200C8EF1F /* PBXContainerItemProxy */; }; + A2B8C14F2A58B70E0054487E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + platformFilter = maccatalyst; + target = A2DBB2F52A00D2DD0017E696 /* AppKitBridge */; + targetProxy = A2B8C14E2A58B70E0054487E /* PBXContainerItemProxy */; + }; + A2DBB3042A00D5F90017E696 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + platformFilter = maccatalyst; + target = A2DBB2F52A00D2DD0017E696 /* AppKitBridge */; + targetProxy = A2DBB3032A00D5F90017E696 /* PBXContainerItemProxy */; + }; + A2F6068E2A1C6D4A0039183A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + platformFilter = maccatalyst; + target = A26D262C2A1C41B1009838E0 /* OutlineLauncher */; + targetProxy = A2F6068D2A1C6D4A0039183A /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -525,13 +674,17 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.outline.ios.client; + "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = org.outline.macos.client; PRODUCT_NAME = Outline; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = iphoneos; STRIP_BITCODE_FROM_COPIED_FILES = NO; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,6"; VALIDATE_WORKSPACE = NO; }; name = Debug; @@ -568,12 +721,16 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.outline.ios.client; + "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = org.outline.macos.client; PRODUCT_NAME = Outline; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = iphoneos; STRIP_BITCODE_FROM_COPIED_FILES = NO; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,6"; VALIDATE_WORKSPACE = NO; }; name = Release; @@ -623,10 +780,12 @@ ); MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = org.outline.ios.client.VpnExtension; + "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = org.outline.macos.client.VpnExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; STRIP_BITCODE_FROM_COPIED_FILES = NO; + SUPPORTS_MACCATALYST = YES; SWIFT_INSTALL_OBJC_HEADER = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -673,10 +832,12 @@ ); MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = org.outline.ios.client.VpnExtension; + "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = org.outline.macos.client.VpnExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; STRIP_BITCODE_FROM_COPIED_FILES = NO; + SUPPORTS_MACCATALYST = YES; SWIFT_INSTALL_OBJC_HEADER = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -684,6 +845,208 @@ }; name = Release; }; + A26D263A2A1C41B4009838E0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = OutlineLauncher/OutlineLauncher.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = QT8Z3Q9V3A; + ENABLE_HARDENED_RUNTIME = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_LSApplicationCategoryType = ""; + INFOPLIST_KEY_LSBackgroundOnly = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.3; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.outline.macos.client.launcher3; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 2; + }; + name = Debug; + }; + A26D263B2A1C41B4009838E0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = OutlineLauncher/OutlineLauncher.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = QT8Z3Q9V3A; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_LSApplicationCategoryType = ""; + INFOPLIST_KEY_LSBackgroundOnly = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.3; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.outline.macos.client.launcher3; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 2; + }; + name = Release; + }; + A2DBB2F82A00D2DD0017E696 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = QT8Z3Q9V3A; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_NSPrincipalClass = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; + MACOSX_DEPLOYMENT_TARGET = 13.3; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.outline.macos.client.AppKitBridge; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "Outline/Classes/AppKitBridge/AppKitBridge-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + WRAPPER_EXTENSION = bundle; + }; + name = Debug; + }; + A2DBB2F92A00D2DD0017E696 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = QT8Z3Q9V3A; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_NSPrincipalClass = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; + MACOSX_DEPLOYMENT_TARGET = 13.3; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.outline.macos.client.AppKitBridge; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "Outline/Classes/AppKitBridge/AppKitBridge-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + WRAPPER_EXTENSION = bundle; + }; + name = Release; + }; C01FCF4F08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 3047A5111AB8059700498E2A /* build.xcconfig */; @@ -720,6 +1083,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 10.15; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SKIP_INSTALL = NO; @@ -762,6 +1126,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 10.15; SDKROOT = iphoneos; SKIP_INSTALL = NO; SWIFT_COMPILATION_MODE = wholemodule; @@ -789,6 +1154,24 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + A26D26392A1C41B4009838E0 /* Build configuration list for PBXNativeTarget "OutlineLauncher" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A26D263A2A1C41B4009838E0 /* Debug */, + A26D263B2A1C41B4009838E0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A2DBB2F72A00D2DD0017E696 /* Build configuration list for PBXNativeTarget "AppKitBridge" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A2DBB2F82A00D2DD0017E696 /* Debug */, + A2DBB2F92A00D2DD0017E696 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Outline" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -820,16 +1203,6 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 52CBB891295BD8F200D0073F /* CocoaLumberjack */ = { - isa = XCSwiftPackageProductDependency; - package = 52CBB890295BD8F200D0073F /* XCRemoteSwiftPackageReference "CocoaLumberjack" */; - productName = CocoaLumberjack; - }; - 52CBB893295BD8F200D0073F /* CocoaLumberjackSwift */ = { - isa = XCSwiftPackageProductDependency; - package = 52CBB890295BD8F200D0073F /* XCRemoteSwiftPackageReference "CocoaLumberjack" */; - productName = CocoaLumberjackSwift; - }; 52CE53E6295B6A310064D03D /* Sentry */ = { isa = XCSwiftPackageProductDependency; package = 52CE53E5295B6A310064D03D /* XCRemoteSwiftPackageReference "sentry-cocoa" */; @@ -839,7 +1212,25 @@ isa = XCSwiftPackageProductDependency; productName = PacketTunnelProvider; }; - F63DC2172970AFFA00D92E0A /* OutlineAppleLib */ = { + A271D41F2A7069D8009981B2 /* OutlineLauncher */ = { + isa = XCSwiftPackageProductDependency; + productName = OutlineLauncher; + }; + A271D4212A706CB9009981B2 /* OutlineAppKitBridge */ = { + isa = XCSwiftPackageProductDependency; + productName = OutlineAppKitBridge; + }; + A271D42F2A708278009981B2 /* CocoaLumberjack */ = { + isa = XCSwiftPackageProductDependency; + package = 52CBB890295BD8F200D0073F /* XCRemoteSwiftPackageReference "CocoaLumberjack" */; + productName = CocoaLumberjack; + }; + A271D4312A708278009981B2 /* CocoaLumberjackSwift */ = { + isa = XCSwiftPackageProductDependency; + package = 52CBB890295BD8F200D0073F /* XCRemoteSwiftPackageReference "CocoaLumberjack" */; + productName = CocoaLumberjackSwift; + }; + A271D4332A70829D009981B2 /* OutlineAppleLib */ = { isa = XCSwiftPackageProductDependency; productName = OutlineAppleLib; }; diff --git a/src/cordova/apple/xcode/ios/Outline/Classes/AppDelegate+Outline.h b/src/cordova/apple/xcode/ios/Outline/Classes/AppDelegate+Outline.h new file mode 100644 index 00000000000..0b0d77f0b2f --- /dev/null +++ b/src/cordova/apple/xcode/ios/Outline/Classes/AppDelegate+Outline.h @@ -0,0 +1,24 @@ +// Copyright 2023 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef AppDelegate_Outline_h +#define AppDelegate_Outline_h + +#import "AppDelegate.h" + +@interface AppDelegate (Outline) +@end + +#endif /* AppDelegate_Outline_h */ + diff --git a/src/cordova/apple/xcode/ios/Outline/Classes/AppDelegate+Outline.m b/src/cordova/apple/xcode/ios/Outline/Classes/AppDelegate+Outline.m new file mode 100644 index 00000000000..65c7e999b7d --- /dev/null +++ b/src/cordova/apple/xcode/ios/Outline/Classes/AppDelegate+Outline.m @@ -0,0 +1,41 @@ +// Copyright 2023 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import +#import +#import "AppDelegate+Outline.h" +#import "Outline-Swift.h" + +#if TARGET_OS_MACCATALYST +@import OutlineCatalystApp; +@import ServiceManagement; +#endif + +@implementation AppDelegate (Outline) + +#pragma mark - Lifecycle + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions: + (NSDictionary *)launchOptions { +#if TARGET_OS_MACCATALYST + [OutlineCatalystApp initApp]; +#endif + + [super application:application didFinishLaunchingWithOptions:launchOptions]; + + return YES; +} + +@end diff --git a/src/cordova/apple/xcode/ios/Outline/Outline-Info.plist b/src/cordova/apple/xcode/ios/Outline/Outline-Info.plist index e0cfd597c96..22020f35c0f 100644 --- a/src/cordova/apple/xcode/ios/Outline/Outline-Info.plist +++ b/src/cordova/apple/xcode/ios/Outline/Outline-Info.plist @@ -40,6 +40,8 @@ ITSAppUsesNonExemptEncryption + LSUIElement + NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/src/cordova/apple/xcode/ios/Outline/Outline.entitlements b/src/cordova/apple/xcode/ios/Outline/Outline.entitlements index 6e3d6122da3..9d88e9d506e 100644 --- a/src/cordova/apple/xcode/ios/Outline/Outline.entitlements +++ b/src/cordova/apple/xcode/ios/Outline/Outline.entitlements @@ -6,9 +6,13 @@ packet-tunnel-provider + com.apple.security.app-sandbox + com.apple.security.application-groups group.org.outline.ios.client + com.apple.security.network.client + diff --git a/src/cordova/apple/xcode/ios/Outline/VpnExtension.entitlements b/src/cordova/apple/xcode/ios/Outline/VpnExtension.entitlements index 6e3d6122da3..fb4bf1a906d 100644 --- a/src/cordova/apple/xcode/ios/Outline/VpnExtension.entitlements +++ b/src/cordova/apple/xcode/ios/Outline/VpnExtension.entitlements @@ -6,9 +6,15 @@ packet-tunnel-provider + com.apple.security.app-sandbox + com.apple.security.application-groups group.org.outline.ios.client + com.apple.security.network.client + + com.apple.security.network.server + diff --git a/src/cordova/apple/xcode/ios/OutlineLauncher/OutlineLauncher.entitlements b/src/cordova/apple/xcode/ios/OutlineLauncher/OutlineLauncher.entitlements new file mode 100644 index 00000000000..bd0f3175861 --- /dev/null +++ b/src/cordova/apple/xcode/ios/OutlineLauncher/OutlineLauncher.entitlements @@ -0,0 +1,18 @@ + + + + + com.apple.developer.networking.networkextension + + packet-tunnel-provider + + com.apple.security.app-sandbox + + com.apple.security.application-groups + + $(TeamIdentifierPrefix)org.outline.macos.client + + com.apple.security.files.user-selected.read-only + + + diff --git a/src/cordova/apple/xcode/macos/Outline/Classes/AppDelegate.m b/src/cordova/apple/xcode/macos/Outline/Classes/AppDelegate.m index 6c681b5f9d3..2769ab40121 100644 --- a/src/cordova/apple/xcode/macos/Outline/Classes/AppDelegate.m +++ b/src/cordova/apple/xcode/macos/Outline/Classes/AppDelegate.m @@ -16,6 +16,7 @@ #import "Outline-Swift.h" @import ServiceManagement; +@import OutlineNotification; @import OutlineTunnel; @interface AppDelegate() @@ -55,13 +56,13 @@ - (void)applicationWillFinishLaunching:(NSNotification *)aNotification { usingBlock:^(NSNotification *_Nonnull n) { self.isSystemShuttingDown = YES; }]; - [NSNotificationCenter.defaultCenter addObserverForName:OutlinePlugin.kVpnConnectedNotification + [NSNotificationCenter.defaultCenter addObserverForName:NSNotification.kVpnConnected object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { [self setAppIcon:@"StatusBarButtonImageConnected"]; }]; - [NSNotificationCenter.defaultCenter addObserverForName:OutlinePlugin.kVpnDisconnectedNotification + [NSNotificationCenter.defaultCenter addObserverForName:NSNotification.kVpnDisconnected object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { @@ -111,7 +112,7 @@ - (void)applicationWillTerminate:(NSNotification *)notification { if (!self.isSystemShuttingDown) { // Don't post a quit notification if the system is shutting down so the VPN is not stopped // and it auto-connects on startup. - [[NSNotificationCenter defaultCenter] postNotificationName:OutlinePlugin.kAppQuitNotification + [[NSNotificationCenter defaultCenter] postNotificationName:NSNotification.kAppQuit object:nil]; } } diff --git a/src/cordova/plugin/apple/src/OutlinePlugin.swift b/src/cordova/plugin/apple/src/OutlinePlugin.swift index 9248154be70..f6ff6ef9c5b 100644 --- a/src/cordova/plugin/apple/src/OutlinePlugin.swift +++ b/src/cordova/plugin/apple/src/OutlinePlugin.swift @@ -18,29 +18,30 @@ import NetworkExtension import Sentry import OutlineSentryLogger +import OutlineNotification import OutlineTunnel @objcMembers class OutlinePlugin: CDVPlugin { - + private enum Action { - static let start = "start" - static let stop = "stop" - static let onStatusChange = "onStatusChange" + static let start = "start" + static let stop = "stop" + static let onStatusChange = "onStatusChange" } - - public static let kAppQuitNotification = "outlinePluginAppQuitNotification" - public static let kVpnConnectedNotification = "outlineVpnConnected" - public static let kVpnDisconnectedNotification = "outlineVpnDisconnected" - public static let kMaxBreadcrumbs: Int = 100 - + + public static let kMaxBreadcrumbs: UInt = 100 + private var sentryLogger: OutlineSentryLogger! private var callbacks: [String: String]! - + #if os(macOS) // cordova-osx does not support URL interception. Until it does, we have version-controlled // AppDelegate.m (intercept) and Outline-Info.plist (register protocol) to handle ss:// URLs. private var urlHandler: CDVMacOsUrlHandler? +#endif +#if os(macOS) || targetEnvironment(macCatalyst) + private static let kPlatform = "macOS" private static let kAppGroup = "QT8Z3Q9V3A.org.outline.macos.client" #else @@ -51,22 +52,25 @@ class OutlinePlugin: CDVPlugin { override func pluginInitialize() { self.sentryLogger = OutlineSentryLogger(forAppGroup: OutlinePlugin.kAppGroup) callbacks = [String: String]() - + OutlineVpn.shared.onVpnStatusChange(onVpnStatusChange) - + #if os(macOS) self.urlHandler = CDVMacOsUrlHandler.init(self.webView) +#endif + +#if os(macOS) || targetEnvironment(macCatalyst) NotificationCenter.default.addObserver( self, selector: #selector(self.stopVpnOnAppQuit), - name: NSNotification.Name(rawValue: OutlinePlugin.kAppQuitNotification), + name: .kAppQuit, object: nil) #endif - + #if os(iOS) self.migrateLocalStorage() #endif } - + /** Starts the VPN. This method is idempotent for a given tunnel. - Parameters: @@ -87,9 +91,8 @@ class OutlinePlugin: CDVPlugin { } OutlineVpn.shared.start(tunnelId, configJson:configJson) { errorCode in if errorCode == OutlineVpn.ErrorCode.noError { -#if os(macOS) - NotificationCenter.default.post( - name: NSNotification.Name(rawValue: OutlinePlugin.kVpnConnectedNotification), object: nil) +#if os(macOS) || targetEnvironment(macCatalyst) + NotificationCenter.default.post(name: .kVpnConnected, object: nil) #endif self.sendSuccess(callbackId: command.callbackId) } else { @@ -98,7 +101,7 @@ class OutlinePlugin: CDVPlugin { } } } - + /** Stops the VPN. Sends an error if the given tunnel is not running. - Parameters: @@ -112,12 +115,11 @@ class OutlinePlugin: CDVPlugin { DDLogInfo("\(Action.stop) \(tunnelId)") OutlineVpn.shared.stop(tunnelId) sendSuccess(callbackId: command.callbackId) -#if os(macOS) - NotificationCenter.default.post( - name: NSNotification.Name(rawValue: OutlinePlugin.kVpnDisconnectedNotification), object: nil) +#if os(macOS) || targetEnvironment(macCatalyst) + NotificationCenter.default.post(name: .kVpnDisconnected, object: nil) #endif } - + func isRunning(_ command: CDVInvokedUrlCommand) { guard let tunnelId = command.argument(at: 0) as? String else { return sendError("Missing tunnel ID", callbackId: command.callbackId) @@ -125,7 +127,7 @@ class OutlinePlugin: CDVPlugin { DDLogInfo("isRunning \(tunnelId)") sendSuccess(OutlineVpn.shared.isActive(tunnelId), callbackId: command.callbackId) } - + func onStatusChange(_ command: CDVInvokedUrlCommand) { guard let tunnelId = command.argument(at: 0) as? String else { return sendError("Missing tunnel ID", callbackId: command.callbackId) @@ -133,9 +135,9 @@ class OutlinePlugin: CDVPlugin { DDLogInfo("\(Action.onStatusChange) \(tunnelId)") setCallbackId(command.callbackId!, action: Action.onStatusChange, tunnelId: tunnelId) } - + // MARK: Error reporting - + func initializeErrorReporting(_ command: CDVInvokedUrlCommand) { DDLogInfo("initializeErrorReporting") guard let sentryDsn = command.argument(at: 0) as? String else { @@ -160,7 +162,7 @@ class OutlinePlugin: CDVPlugin { } sendSuccess(true, callbackId: command.callbackId) } - + func reportEvents(_ command: CDVInvokedUrlCommand) { var uuid: String if let eventId = command.argument(at: 0) as? String { @@ -172,76 +174,74 @@ class OutlinePlugin: CDVPlugin { } else { uuid = NSUUID().uuidString } - self.sentryLogger.addVpnExtensionLogsToSentry(maxBreadcrumbsToAdd: OutlinePlugin.kMaxBreadcrumbs / 2) + self.sentryLogger.addVpnExtensionLogsToSentry(maxBreadcrumbsToAdd: Int(OutlinePlugin.kMaxBreadcrumbs / 2)) SentrySDK.capture(message: "\(OutlinePlugin.kPlatform) report (\(uuid))") { scope in scope.setLevel(.info) } self.sendSuccess(true, callbackId: command.callbackId) } - + #if os(macOS) func quitApplication(_ command: CDVInvokedUrlCommand) { NSApplication.shared.terminate(self) } #endif - - // MARK: Helpers - - @objc private func stopVpnOnAppQuit() { - if let activeTunnelId = OutlineVpn.shared.activeTunnelId { - OutlineVpn.shared.stop(activeTunnelId) - } + + // MARK: Helpers + + @objc private func stopVpnOnAppQuit() { + if let activeTunnelId = OutlineVpn.shared.activeTunnelId { + OutlineVpn.shared.stop(activeTunnelId) } - - // Receives NEVPNStatusDidChange notifications. Calls onTunnelStatusChange for the active - // tunnel. - func onVpnStatusChange(vpnStatus: NEVPNStatus, tunnelId: String) { - var tunnelStatus: Int - switch vpnStatus { - case .connected: -#if os(macOS) - NotificationCenter.default.post( - name: NSNotification.Name(rawValue: OutlinePlugin.kVpnConnectedNotification), object: nil) + } + + // Receives NEVPNStatusDidChange notifications. Calls onTunnelStatusChange for the active + // tunnel. + func onVpnStatusChange(vpnStatus: NEVPNStatus, tunnelId: String) { + var tunnelStatus: Int + switch vpnStatus { + case .connected: +#if os(macOS) || targetEnvironment(macCatalyst) + NotificationCenter.default.post(name: .kVpnConnected, object: nil) #endif - tunnelStatus = OutlineTunnel.TunnelStatus.connected.rawValue - case .disconnected: -#if os(macOS) - NotificationCenter.default.post( - name: NSNotification.Name(rawValue: OutlinePlugin.kVpnDisconnectedNotification), object: nil) + tunnelStatus = OutlineTunnel.TunnelStatus.connected.rawValue + case .disconnected: +#if os(macOS) || targetEnvironment(macCatalyst) + NotificationCenter.default.post(name: .kVpnDisconnected, object: nil) #endif - tunnelStatus = OutlineTunnel.TunnelStatus.disconnected.rawValue - case .reasserting: - tunnelStatus = OutlineTunnel.TunnelStatus.reconnecting.rawValue - default: - return; // Do not report transient or invalid states. - } - DDLogDebug("Calling onStatusChange (\(tunnelStatus)) for tunnel \(tunnelId)") - if let callbackId = getCallbackIdFor(action: Action.onStatusChange, - tunnelId: tunnelId, - keepCallback: true) { - let result = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: Int32(tunnelStatus)) - send(pluginResult: result, callbackId: callbackId, keepCallback: true) - } + tunnelStatus = OutlineTunnel.TunnelStatus.disconnected.rawValue + case .reasserting: + tunnelStatus = OutlineTunnel.TunnelStatus.reconnecting.rawValue + default: + return; // Do not report transient or invalid states. + } + DDLogDebug("Calling onStatusChange (\(tunnelStatus)) for tunnel \(tunnelId)") + if let callbackId = getCallbackIdFor(action: Action.onStatusChange, + tunnelId: tunnelId, + keepCallback: true) { + let result = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: Int32(tunnelStatus)) + send(pluginResult: result, callbackId: callbackId, keepCallback: true) } - - // Returns whether |config| contains all the expected keys - private func containsExpectedKeys(_ configJson: [String: Any]?) -> Bool { - return configJson?["host"] != nil && configJson?["port"] != nil && + } + + // Returns whether |config| contains all the expected keys + private func containsExpectedKeys(_ configJson: [String: Any]?) -> Bool { + return configJson?["host"] != nil && configJson?["port"] != nil && configJson?["password"] != nil && configJson?["method"] != nil } - + // MARK: Callback helpers - + private func sendSuccess(callbackId: String, keepCallback: Bool = false) { let result = CDVPluginResult(status: CDVCommandStatus_OK) send(pluginResult: result, callbackId: callbackId, keepCallback: keepCallback) } - + private func sendSuccess(_ operationResult: Bool, callbackId: String, keepCallback: Bool = false) { let result = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: operationResult) send(pluginResult: result, callbackId: callbackId, keepCallback: keepCallback) } - + private func sendError(_ message: String, callbackId: String, errorCode: OutlineVpn.ErrorCode = OutlineVpn.ErrorCode.undefined, keepCallback: Bool = false) { @@ -250,7 +250,7 @@ class OutlinePlugin: CDVPlugin { messageAs: Int32(errorCode.rawValue)) send(pluginResult: result, callbackId: callbackId, keepCallback: keepCallback) } - + private func send(pluginResult: CDVPluginResult?, callbackId: String, keepCallback: Bool) { guard let result = pluginResult else { return DDLogWarn("Missing plugin result"); @@ -258,13 +258,13 @@ class OutlinePlugin: CDVPlugin { result.setKeepCallbackAs(keepCallback) self.commandDelegate?.send(result, callbackId: callbackId) } - + // Maps |action| and |tunnelId| to |callbackId| in the callbacks dictionary. private func setCallbackId(_ callbackId: String, action: String, tunnelId: String) { DDLogDebug("\(action):\(tunnelId):\(callbackId)") callbacks["\(action):\(tunnelId)"] = callbackId } - + // Retrieves the callback ID for |action| and |tunnelId|. Unmaps the entry if |keepCallback| // is false. private func getCallbackIdFor(action: String, tunnelId: String?, @@ -282,67 +282,75 @@ class OutlinePlugin: CDVPlugin { } return callbackId } - + // Migrates local storage files from UIWebView to WKWebView. private func migrateLocalStorage() { - // Local storage backing files have the following naming format: $scheme_$hostname_$port.localstorage - // With UIWebView, the app used the file:// scheme with no hostname and any port. - let kUIWebViewLocalStorageFilename = "file__0.localstorage" - // With WKWebView, the app uses the app:// scheme with localhost as a hostname and any port. - let kWKWebViewLocalStorageFilename = "app_localhost_0.localstorage" - - let fileManager = FileManager.default - let appLibraryDir = fileManager.urls(for: .libraryDirectory, in: .userDomainMask)[0] - - var uiWebViewLocalStorageDir: URL + // Local storage backing files have the following naming format: $scheme_$hostname_$port.localstorage + // With UIWebView, the app used the file:// scheme with no hostname and any port. + let kUIWebViewLocalStorageFilename = "file__0.localstorage" + // With WKWebView, the app uses the app:// scheme with localhost as a hostname and any port. + let kWKWebViewLocalStorageFilename = "app_localhost_0.localstorage" + + let fileManager = FileManager.default + let appLibraryDir = fileManager.urls(for: .libraryDirectory, in: .userDomainMask)[0] + + let uiWebViewLocalStorageDir: URL +#if targetEnvironment(macCatalyst) + guard let bundleID = Bundle.main.bundleIdentifier else { + return DDLogError("Unable to get bundleID for app.") + } + let appSupportDir = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] + uiWebViewLocalStorageDir = appSupportDir.appendingPathComponent(bundleID) +#else if fileManager.fileExists(atPath: appLibraryDir.appendingPathComponent( "WebKit/LocalStorage/\(kUIWebViewLocalStorageFilename)").relativePath) { - uiWebViewLocalStorageDir = appLibraryDir.appendingPathComponent("WebKit/LocalStorage") + uiWebViewLocalStorageDir = appLibraryDir.appendingPathComponent("WebKit/LocalStorage") } else { - uiWebViewLocalStorageDir = appLibraryDir.appendingPathComponent("Caches") - } - let uiWebViewLocalStorage = uiWebViewLocalStorageDir.appendingPathComponent(kUIWebViewLocalStorageFilename) - if !fileManager.fileExists(atPath: uiWebViewLocalStorage.relativePath) { - return DDLogInfo("Not migrating, UIWebView local storage files missing.") - } - - let wkWebViewLocalStorageDir = appLibraryDir.appendingPathComponent("WebKit/WebsiteData/LocalStorage/") - let wkWebViewLocalStorage = wkWebViewLocalStorageDir.appendingPathComponent(kWKWebViewLocalStorageFilename) - // Only copy the local storage files if they don't exist for WKWebView. - if fileManager.fileExists(atPath: wkWebViewLocalStorage.relativePath) { - return DDLogInfo("Not migrating, WKWebView local storage files present.") - } - DDLogInfo("Migrating UIWebView local storage to WKWebView") - - // Create the WKWebView local storage directory; this is safe if the directory already exists. - do { - try fileManager.createDirectory(at: wkWebViewLocalStorageDir, withIntermediateDirectories: true) - } catch { - return DDLogError("Failed to create WKWebView local storage directory") - } - - // Create a tmp directory and copy onto it the local storage files. - guard let tmpDir = try? fileManager.url(for: .itemReplacementDirectory, in: .userDomainMask, - appropriateFor: wkWebViewLocalStorage, create: true) else { - return DDLogError("Failed to create tmp dir") - } - do { - try fileManager.copyItem(at: uiWebViewLocalStorage, - to: tmpDir.appendingPathComponent(wkWebViewLocalStorage.lastPathComponent)) - try fileManager.copyItem(at: URL.init(fileURLWithPath: "\(uiWebViewLocalStorage.relativePath)-shm"), - to: tmpDir.appendingPathComponent("\(kWKWebViewLocalStorageFilename)-shm")) - try fileManager.copyItem(at: URL.init(fileURLWithPath: "\(uiWebViewLocalStorage.relativePath)-wal"), - to: tmpDir.appendingPathComponent("\(kWKWebViewLocalStorageFilename)-wal")) - } catch { - return DDLogError("Local storage migration failed.") - } - - // Atomically move the tmp directory to the WKWebView local storage directory. - guard let _ = try? fileManager.replaceItemAt(wkWebViewLocalStorageDir, withItemAt: tmpDir, - backupItemName: nil, options: .usingNewMetadataOnly) else { - return DDLogError("Failed to copy tmp dir to WKWebView local storage dir") + uiWebViewLocalStorageDir = appLibraryDir.appendingPathComponent("Caches") } - - DDLogInfo("Local storage migration succeeded") +#endif + let uiWebViewLocalStorage = uiWebViewLocalStorageDir.appendingPathComponent(kUIWebViewLocalStorageFilename) + if !fileManager.fileExists(atPath: uiWebViewLocalStorage.relativePath) { + return DDLogInfo("Not migrating, UIWebView local storage files missing.") + } + + let wkWebViewLocalStorageDir = appLibraryDir.appendingPathComponent("WebKit/WebsiteData/LocalStorage/") + let wkWebViewLocalStorage = wkWebViewLocalStorageDir.appendingPathComponent(kWKWebViewLocalStorageFilename) + // Only copy the local storage files if they don't exist for WKWebView. + if fileManager.fileExists(atPath: wkWebViewLocalStorage.relativePath) { + return DDLogInfo("Not migrating, WKWebView local storage files present.") + } + DDLogInfo("Migrating UIWebView local storage to WKWebView") + + // Create the WKWebView local storage directory; this is safe if the directory already exists. + do { + try fileManager.createDirectory(at: wkWebViewLocalStorageDir, withIntermediateDirectories: true) + } catch { + return DDLogError("Failed to create WKWebView local storage directory") + } + + // Create a tmp directory and copy onto it the local storage files. + guard let tmpDir = try? fileManager.url(for: .itemReplacementDirectory, in: .userDomainMask, + appropriateFor: wkWebViewLocalStorage, create: true) else { + return DDLogError("Failed to create tmp dir") + } + do { + try fileManager.copyItem(at: uiWebViewLocalStorage, + to: tmpDir.appendingPathComponent(wkWebViewLocalStorage.lastPathComponent)) + try fileManager.copyItem(at: URL.init(fileURLWithPath: "\(uiWebViewLocalStorage.relativePath)-shm"), + to: tmpDir.appendingPathComponent("\(kWKWebViewLocalStorageFilename)-shm")) + try fileManager.copyItem(at: URL.init(fileURLWithPath: "\(uiWebViewLocalStorage.relativePath)-wal"), + to: tmpDir.appendingPathComponent("\(kWKWebViewLocalStorageFilename)-wal")) + } catch { + return DDLogError("Local storage migration failed.") + } + + // Atomically move the tmp directory to the WKWebView local storage directory. + guard let _ = try? fileManager.replaceItemAt(wkWebViewLocalStorageDir, withItemAt: tmpDir, + backupItemName: nil, options: .usingNewMetadataOnly) else { + return DDLogError("Failed to copy tmp dir to WKWebView local storage dir") + } + + DDLogInfo("Local storage migration succeeded") } -} + }