mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
Bring in apple client into monorepo (#1737)
This PR brings in the apple client into the monorepo. --------- Co-authored-by: Jamil <jamilbk@users.noreply.github.com>
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
[codespell]
|
||||
skip = ./rust/target,Cargo.lock,./www/docs/reference/api/*.mdx,./erl_crash.dump,./apps/*/erl_crash.dump,./cover,./vendor,*.json,yarn.lock,seeds.exs,./**/node_modules,./deps,./priv/static,./priv/plts,./**/priv/static,./.git,./www/build,./_build
|
||||
ignore-words-list = crate,keypair,keypairs,iif,statics,wee,anull,commitish
|
||||
ignore-words-list = crate,keypair,keypairs,iif,statics,wee,anull,commitish,inout
|
||||
|
||||
46
.github/workflows/swift.yml
vendored
46
.github/workflows/swift.yml
vendored
@@ -3,6 +3,7 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "swift/**"
|
||||
- "rust/connlib/**"
|
||||
- ".github/workflows/swift.yml"
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
@@ -26,8 +27,6 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# TODO: Add a basic CI for the Apple client
|
||||
# See rust.yml how we build, package and release connlib as an example
|
||||
build:
|
||||
runs-on: macos-latest
|
||||
permissions:
|
||||
@@ -39,4 +38,45 @@ jobs:
|
||||
- draft-release
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
# TODO: Build Apple client from the CLI
|
||||
- run: rustup show
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: ./rust
|
||||
prefix-key: rust-${{ matrix.runs-on }}
|
||||
save-if: ${{ github.ref == 'refs/heads/cloud' }}
|
||||
- name: Update toolchain
|
||||
run: rustup show
|
||||
- name: Setup lipo
|
||||
run: cargo install cargo-lipo
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: apple/.build
|
||||
key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-spm-
|
||||
- name: Build Connlib.xcframework
|
||||
env:
|
||||
CONFIGURATION: Release
|
||||
PROJECT_DIR: .
|
||||
working-directory: ./rust/connlib/clients/apple
|
||||
run: |
|
||||
# build-xcframework.sh calls build-rust.sh indirectly via `xcodebuild`, but it pollutes the environment
|
||||
# to the point that it causes the `ring` build to fail for the aarch64-apple-darwin target. So, explicitly
|
||||
# build first. See https://github.com/briansmith/ring/issues/1332
|
||||
PLATFORM_NAME=macosx ./build-rust.sh
|
||||
PLATFORM_NAME=iphoneos ./build-rust.sh
|
||||
./build-xcframework-dev.sh
|
||||
- name: Select Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: latest-stable
|
||||
- name: Build app for macOS
|
||||
working-directory: ./swift/apple
|
||||
run: |
|
||||
cp Firezone/Developer.xcconfig.ci-macOS Firezone/Developer.xcconfig
|
||||
xcodebuild build -scheme Firezone -sdk macosx -destination 'platform=macOS' CODE_SIGNING_ALLOWED=NO
|
||||
- name: Build app for iOS
|
||||
working-directory: ./swift/apple
|
||||
run: |
|
||||
cp Firezone/Developer.xcconfig.ci-iOS Firezone/Developer.xcconfig
|
||||
xcodebuild build -scheme Firezone -sdk iphoneos -destination 'generic/platform=iOS' CODE_SIGNING_ALLOWED=NO
|
||||
|
||||
@@ -6,13 +6,6 @@
|
||||
|
||||
set -ex
|
||||
|
||||
if [[ -z "$PROJECT_DIR" ]]; then
|
||||
echo "Must provide PROJECT_DIR environment variable set to the Xcode project directory." 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd $PROJECT_DIR
|
||||
|
||||
# Default PLATFORM_NAME to macosx if not set.
|
||||
: "${PLATFORM_NAME:=macosx}"
|
||||
|
||||
|
||||
8
swift/apple/.gitignore
vendored
Normal file
8
swift/apple/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
.DS_Store
|
||||
.build/
|
||||
build/
|
||||
DerivedData/
|
||||
xcuserdata/
|
||||
**/*.xcuserstate
|
||||
|
||||
Firezone/Developer.xcconfig
|
||||
13
swift/apple/.swiftformat
Normal file
13
swift/apple/.swiftformat
Normal file
@@ -0,0 +1,13 @@
|
||||
--swiftversion 5.7
|
||||
--binarygrouping none
|
||||
--decimalgrouping none
|
||||
--hexgrouping none
|
||||
--indent 2
|
||||
--octalgrouping none
|
||||
--semicolons never
|
||||
--wraparguments before-first
|
||||
--wrapcollections before-first
|
||||
--wrapparameters before-first
|
||||
--extensionacl on-declarations
|
||||
--maxwidth 100
|
||||
--header \n {file}\n (c) {created.year} Firezone, Inc.\n LICENSE: Apache-2.0\n
|
||||
797
swift/apple/Firezone.xcodeproj/project.pbxproj
Normal file
797
swift/apple/Firezone.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,797 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 56;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
05CF1CF1290B1CEE00CF4755 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05D3BB1628FDBD8A00BC3727 /* NetworkExtension.framework */; };
|
||||
05CF1CF9290B1CEE00CF4755 /* FirezoneNetworkExtensioniOS.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 05CF1CF0290B1CEE00CF4755 /* FirezoneNetworkExtensioniOS.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
05CF1D04290B1DCD00CF4755 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05D3BB1628FDBD8A00BC3727 /* NetworkExtension.framework */; platformFilters = (macos, ); };
|
||||
05CF1D0C290B1DCD00CF4755 /* FirezoneNetworkExtensionmacOS.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 05CF1D03290B1DCD00CF4755 /* FirezoneNetworkExtensionmacOS.appex */; platformFilters = (macos, ); settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
05CF1D16290B1FE700CF4755 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05833DFA28F73B070008FAB0 /* PacketTunnelProvider.swift */; };
|
||||
05CF1D17290B1FE700CF4755 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05833DFA28F73B070008FAB0 /* PacketTunnelProvider.swift */; };
|
||||
05D3BB2128FDE9C000BC3727 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05D3BB1628FDBD8A00BC3727 /* NetworkExtension.framework */; };
|
||||
794C38152970A2660029F38F /* FirezoneKit in Frameworks */ = {isa = PBXBuildFile; productRef = 794C38142970A2660029F38F /* FirezoneKit */; };
|
||||
794C38172970A26A0029F38F /* FirezoneKit in Frameworks */ = {isa = PBXBuildFile; productRef = 794C38162970A26A0029F38F /* FirezoneKit */; };
|
||||
79756C6629704A7A0018E2D5 /* FirezoneKit in Frameworks */ = {isa = PBXBuildFile; productRef = 79756C6529704A7A0018E2D5 /* FirezoneKit */; };
|
||||
8DCC021D28D512AC007E12D2 /* FirezoneApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DCC021C28D512AC007E12D2 /* FirezoneApp.swift */; };
|
||||
8DCC022628D512AE007E12D2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8DCC022528D512AE007E12D2 /* Assets.xcassets */; };
|
||||
8DCC022A28D512AE007E12D2 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8DCC022928D512AE007E12D2 /* Preview Assets.xcassets */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
05CF1CF7290B1CEE00CF4755 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 8DCC021128D512AC007E12D2 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 05CF1CEF290B1CEE00CF4755;
|
||||
remoteInfo = FirezoneNetworkExtensioniOS;
|
||||
};
|
||||
05CF1D0A290B1DCD00CF4755 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 8DCC021128D512AC007E12D2 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 05CF1D02290B1DCD00CF4755;
|
||||
remoteInfo = FirezoneNetworkExtensionmacOS;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
0556189428EF883500DF9E3C /* Embed Foundation Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
05CF1CF9290B1CEE00CF4755 /* FirezoneNetworkExtensioniOS.appex in Embed Foundation Extensions */,
|
||||
05CF1D0C290B1DCD00CF4755 /* FirezoneNetworkExtensionmacOS.appex in Embed Foundation Extensions */,
|
||||
);
|
||||
name = "Embed Foundation Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
05CF1C8A290B11A500CF4755 /* Embed System Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "$(SYSTEM_EXTENSIONS_FOLDER_PATH)";
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
);
|
||||
name = "Embed System Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
05833DFA28F73B070008FAB0 /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = "<group>"; };
|
||||
05B6467B292C36140014A4D4 /* AuthenticationServiceStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceStub.swift; sourceTree = "<group>"; };
|
||||
05CF1C39290995DA00CF4755 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
05CF1CDE290B1A9000CF4755 /* FirezoneNetworkExtension_macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FirezoneNetworkExtension_macOS.entitlements; sourceTree = "<group>"; };
|
||||
05CF1CF0290B1CEE00CF4755 /* FirezoneNetworkExtensioniOS.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FirezoneNetworkExtensioniOS.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
05CF1CF6290B1CEE00CF4755 /* FirezoneNetworkExtension_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FirezoneNetworkExtension_iOS.entitlements; sourceTree = "<group>"; };
|
||||
05CF1D03290B1DCD00CF4755 /* FirezoneNetworkExtensionmacOS.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FirezoneNetworkExtensionmacOS.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
05D3BB1628FDBD8A00BC3727 /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
|
||||
79BA2AF229A00F8800A2E6DC /* Developer.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Developer.xcconfig; sourceTree = "<group>"; };
|
||||
79C88B24296F494500261800 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
|
||||
8DCC021928D512AC007E12D2 /* Firezone.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Firezone.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
8DCC021C28D512AC007E12D2 /* FirezoneApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirezoneApp.swift; sourceTree = "<group>"; };
|
||||
8DCC022528D512AE007E12D2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
8DCC022728D512AE007E12D2 /* Firezone.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Firezone.entitlements; sourceTree = "<group>"; };
|
||||
8DCC022928D512AE007E12D2 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
8DD2C4C3297B37BA00F984BF /* FirezoneKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = FirezoneKit; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
05CF1CED290B1CEE00CF4755 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
794C38152970A2660029F38F /* FirezoneKit in Frameworks */,
|
||||
05CF1CF1290B1CEE00CF4755 /* NetworkExtension.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
05CF1D00290B1DCD00CF4755 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
794C38172970A26A0029F38F /* FirezoneKit in Frameworks */,
|
||||
05CF1D04290B1DCD00CF4755 /* NetworkExtension.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
8DCC021628D512AC007E12D2 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
05D3BB2128FDE9C000BC3727 /* NetworkExtension.framework in Frameworks */,
|
||||
79756C6629704A7A0018E2D5 /* FirezoneKit in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
0580451B29385C150080D1F0 /* Recovered References */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
05B6467B292C36140014A4D4 /* AuthenticationServiceStub.swift */,
|
||||
);
|
||||
name = "Recovered References";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
05833DF928F73B070008FAB0 /* FirezoneNetworkExtension */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
05CF1CF6290B1CEE00CF4755 /* FirezoneNetworkExtension_iOS.entitlements */,
|
||||
05CF1CDE290B1A9000CF4755 /* FirezoneNetworkExtension_macOS.entitlements */,
|
||||
05833DFA28F73B070008FAB0 /* PacketTunnelProvider.swift */,
|
||||
);
|
||||
path = FirezoneNetworkExtension;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
05E1505F28FF398000170F82 /* Application */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8DCC021C28D512AC007E12D2 /* FirezoneApp.swift */,
|
||||
);
|
||||
path = Application;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8D3F90C328D64FAD00980124 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
05D3BB1628FDBD8A00BC3727 /* NetworkExtension.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8DCC021028D512AC007E12D2 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8DD2C4C2297B37BA00F984BF /* Packages */,
|
||||
8DCC021B28D512AC007E12D2 /* Firezone */,
|
||||
05833DF928F73B070008FAB0 /* FirezoneNetworkExtension */,
|
||||
8DCC021A28D512AC007E12D2 /* Products */,
|
||||
8D3F90C328D64FAD00980124 /* Frameworks */,
|
||||
0580451B29385C150080D1F0 /* Recovered References */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8DCC021A28D512AC007E12D2 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8DCC021928D512AC007E12D2 /* Firezone.app */,
|
||||
05CF1CF0290B1CEE00CF4755 /* FirezoneNetworkExtensioniOS.appex */,
|
||||
05CF1D03290B1DCD00CF4755 /* FirezoneNetworkExtensionmacOS.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8DCC021B28D512AC007E12D2 /* Firezone */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
05E1505F28FF398000170F82 /* Application */,
|
||||
8DCC022528D512AE007E12D2 /* Assets.xcassets */,
|
||||
79C88B24296F494500261800 /* Config.xcconfig */,
|
||||
79BA2AF229A00F8800A2E6DC /* Developer.xcconfig */,
|
||||
8DCC022728D512AE007E12D2 /* Firezone.entitlements */,
|
||||
05CF1C39290995DA00CF4755 /* Info.plist */,
|
||||
8DCC022828D512AE007E12D2 /* Preview Content */,
|
||||
);
|
||||
path = Firezone;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8DCC022828D512AE007E12D2 /* Preview Content */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8DCC022928D512AE007E12D2 /* Preview Assets.xcassets */,
|
||||
);
|
||||
path = "Preview Content";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8DD2C4C2297B37BA00F984BF /* Packages */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8DD2C4C3297B37BA00F984BF /* FirezoneKit */,
|
||||
);
|
||||
name = Packages;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
05CF1CEF290B1CEE00CF4755 /* FirezoneNetworkExtensioniOS */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 05CF1CFA290B1CEE00CF4755 /* Build configuration list for PBXNativeTarget "FirezoneNetworkExtensioniOS" */;
|
||||
buildPhases = (
|
||||
05CF1CEC290B1CEE00CF4755 /* Sources */,
|
||||
05CF1CED290B1CEE00CF4755 /* Frameworks */,
|
||||
05CF1CEE290B1CEE00CF4755 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = FirezoneNetworkExtensioniOS;
|
||||
packageProductDependencies = (
|
||||
794C38142970A2660029F38F /* FirezoneKit */,
|
||||
);
|
||||
productName = FirezoneNetworkExtensioniOS;
|
||||
productReference = 05CF1CF0290B1CEE00CF4755 /* FirezoneNetworkExtensioniOS.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
05CF1D02290B1DCD00CF4755 /* FirezoneNetworkExtensionmacOS */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 05CF1D0D290B1DCD00CF4755 /* Build configuration list for PBXNativeTarget "FirezoneNetworkExtensionmacOS" */;
|
||||
buildPhases = (
|
||||
05CF1CFF290B1DCD00CF4755 /* Sources */,
|
||||
05CF1D00290B1DCD00CF4755 /* Frameworks */,
|
||||
05CF1D01290B1DCD00CF4755 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = FirezoneNetworkExtensionmacOS;
|
||||
packageProductDependencies = (
|
||||
794C38162970A26A0029F38F /* FirezoneKit */,
|
||||
);
|
||||
productName = FirezoneNetworkExtensionmacOS;
|
||||
productReference = 05CF1D03290B1DCD00CF4755 /* FirezoneNetworkExtensionmacOS.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
8DCC021828D512AC007E12D2 /* Firezone */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 8DCC024328D512AE007E12D2 /* Build configuration list for PBXNativeTarget "Firezone" */;
|
||||
buildPhases = (
|
||||
8DCC021528D512AC007E12D2 /* Sources */,
|
||||
8DCC021628D512AC007E12D2 /* Frameworks */,
|
||||
8DCC021728D512AC007E12D2 /* Resources */,
|
||||
0556189428EF883500DF9E3C /* Embed Foundation Extensions */,
|
||||
05CF1C8A290B11A500CF4755 /* Embed System Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
05CF1CF8290B1CEE00CF4755 /* PBXTargetDependency */,
|
||||
05CF1D0B290B1DCD00CF4755 /* PBXTargetDependency */,
|
||||
);
|
||||
name = Firezone;
|
||||
packageProductDependencies = (
|
||||
79756C6529704A7A0018E2D5 /* FirezoneKit */,
|
||||
);
|
||||
productName = Firezone;
|
||||
productReference = 8DCC021928D512AC007E12D2 /* Firezone.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
8DCC021128D512AC007E12D2 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1400;
|
||||
LastUpgradeCheck = 1420;
|
||||
TargetAttributes = {
|
||||
05CF1CEF290B1CEE00CF4755 = {
|
||||
CreatedOnToolsVersion = 14.0.1;
|
||||
};
|
||||
05CF1D02290B1DCD00CF4755 = {
|
||||
CreatedOnToolsVersion = 14.0.1;
|
||||
};
|
||||
8DCC021828D512AC007E12D2 = {
|
||||
CreatedOnToolsVersion = 14.0;
|
||||
LastSwiftMigration = 1400;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 8DCC021428D512AC007E12D2 /* Build configuration list for PBXProject "Firezone" */;
|
||||
compatibilityVersion = "Xcode 14.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 8DCC021028D512AC007E12D2;
|
||||
packageReferences = (
|
||||
);
|
||||
productRefGroup = 8DCC021A28D512AC007E12D2 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
8DCC021828D512AC007E12D2 /* Firezone */,
|
||||
05CF1CEF290B1CEE00CF4755 /* FirezoneNetworkExtensioniOS */,
|
||||
05CF1D02290B1DCD00CF4755 /* FirezoneNetworkExtensionmacOS */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
05CF1CEE290B1CEE00CF4755 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
05CF1D01290B1DCD00CF4755 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
8DCC021728D512AC007E12D2 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8DCC022A28D512AE007E12D2 /* Preview Assets.xcassets in Resources */,
|
||||
8DCC022628D512AE007E12D2 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
05CF1CEC290B1CEE00CF4755 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
05CF1D17290B1FE700CF4755 /* PacketTunnelProvider.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
05CF1CFF290B1DCD00CF4755 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
05CF1D16290B1FE700CF4755 /* PacketTunnelProvider.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
8DCC021528D512AC007E12D2 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8DCC021D28D512AC007E12D2 /* FirezoneApp.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
05CF1CF8290B1CEE00CF4755 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
platformFilter = ios;
|
||||
target = 05CF1CEF290B1CEE00CF4755 /* FirezoneNetworkExtensioniOS */;
|
||||
targetProxy = 05CF1CF7290B1CEE00CF4755 /* PBXContainerItemProxy */;
|
||||
};
|
||||
05CF1D0B290B1DCD00CF4755 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
platformFilters = (
|
||||
macos,
|
||||
);
|
||||
target = 05CF1D02290B1DCD00CF4755 /* FirezoneNetworkExtensionmacOS */;
|
||||
targetProxy = 05CF1D0A290B1DCD00CF4755 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
05CF1CFB290B1CEE00CF4755 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = FirezoneNetworkExtension/FirezoneNetworkExtension_iOS.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
FRAMEWORK_SEARCH_PATHS = "";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = FirezoneNetworkExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = FirezoneNetworkExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
MARKETING_VERSION = 1.0;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).network-extension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_INTERFACE_HEADER_NAME = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TVOS_DEPLOYMENT_TARGET = "";
|
||||
WATCHOS_DEPLOYMENT_TARGET = "";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
05CF1CFC290B1CEE00CF4755 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = FirezoneNetworkExtension/FirezoneNetworkExtension_iOS.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
FRAMEWORK_SEARCH_PATHS = "";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = FirezoneNetworkExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = FirezoneNetworkExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).network-extension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_INTERFACE_HEADER_NAME = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TVOS_DEPLOYMENT_TARGET = "";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
WATCHOS_DEPLOYMENT_TARGET = "";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
05CF1D0E290B1DCD00CF4755 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = FirezoneNetworkExtension/FirezoneNetworkExtension_macOS.entitlements;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = FirezoneNetworkExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = FirezoneNetworkExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).network-extension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = macosx;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_INTERFACE_HEADER_NAME = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TVOS_DEPLOYMENT_TARGET = "";
|
||||
WATCHOS_DEPLOYMENT_TARGET = "";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
05CF1D0F290B1DCD00CF4755 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = FirezoneNetworkExtension/FirezoneNetworkExtension_macOS.entitlements;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = FirezoneNetworkExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = FirezoneNetworkExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(inherited).network-extension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = macosx;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_INTERFACE_HEADER_NAME = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TVOS_DEPLOYMENT_TARGET = "";
|
||||
WATCHOS_DEPLOYMENT_TARGET = "";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
8DCC024128D512AE007E12D2 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 79C88B24296F494500261800 /* Config.xcconfig */;
|
||||
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_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
8DCC024228D512AE007E12D2 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 79C88B24296F494500261800 /* Config.xcconfig */;
|
||||
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_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos";
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
8DCC024428D512AE007E12D2 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Firezone/Firezone.entitlements;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEFINES_MODULE = NO;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Firezone/Preview Content\"";
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Firezone/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Firezone;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
INFOPLIST_KEY_LSUIElement = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
|
||||
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
|
||||
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
|
||||
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
|
||||
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_INSTALL_MODULE = YES;
|
||||
SWIFT_INSTALL_OBJC_HEADER = NO;
|
||||
SWIFT_OBJC_INTERFACE_HEADER_NAME = "";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TVOS_DEPLOYMENT_TARGET = "";
|
||||
WATCHOS_DEPLOYMENT_TARGET = "";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
8DCC024528D512AE007E12D2 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Firezone/Firezone.entitlements;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEFINES_MODULE = NO;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Firezone/Preview Content\"";
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Firezone/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Firezone;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
INFOPLIST_KEY_LSUIElement = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
|
||||
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
|
||||
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
|
||||
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
|
||||
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_MODULE_NAME = "$(PRODUCT_NAME:c99extidentifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_INSTALL_MODULE = NO;
|
||||
SWIFT_INSTALL_OBJC_HEADER = NO;
|
||||
SWIFT_OBJC_INTERFACE_HEADER_NAME = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TVOS_DEPLOYMENT_TARGET = "";
|
||||
WATCHOS_DEPLOYMENT_TARGET = "";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
05CF1CFA290B1CEE00CF4755 /* Build configuration list for PBXNativeTarget "FirezoneNetworkExtensioniOS" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
05CF1CFB290B1CEE00CF4755 /* Debug */,
|
||||
05CF1CFC290B1CEE00CF4755 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
05CF1D0D290B1DCD00CF4755 /* Build configuration list for PBXNativeTarget "FirezoneNetworkExtensionmacOS" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
05CF1D0E290B1DCD00CF4755 /* Debug */,
|
||||
05CF1D0F290B1DCD00CF4755 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
8DCC021428D512AC007E12D2 /* Build configuration list for PBXProject "Firezone" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
8DCC024128D512AE007E12D2 /* Debug */,
|
||||
8DCC024228D512AE007E12D2 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
8DCC024328D512AE007E12D2 /* Build configuration list for PBXNativeTarget "Firezone" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
8DCC024428D512AE007E12D2 /* Debug */,
|
||||
8DCC024528D512AE007E12D2 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
794C38142970A2660029F38F /* FirezoneKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = FirezoneKit;
|
||||
};
|
||||
794C38162970A26A0029F38F /* FirezoneKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = FirezoneKit;
|
||||
};
|
||||
79756C6529704A7A0018E2D5 /* FirezoneKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = FirezoneKit;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 8DCC021128D512AC007E12D2 /* Project object */;
|
||||
}
|
||||
7
swift/apple/Firezone.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
swift/apple/Firezone.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "combine-schedulers",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/combine-schedulers",
|
||||
"state" : {
|
||||
"revision" : "0625932976b3ae23949f6b816d13bd97f3b40b7c",
|
||||
"version" : "0.10.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "jwtdecode.swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/auth0/JWTDecode.swift",
|
||||
"state" : {
|
||||
"revision" : "7918a343e674c7707e0be120bb4e21d679be014c",
|
||||
"version" : "3.0.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-case-paths",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/swift-case-paths",
|
||||
"state" : {
|
||||
"revision" : "fc45e7b2cfece9dd80b5a45e6469ffe67fe67984",
|
||||
"version" : "0.14.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-clocks",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/swift-clocks",
|
||||
"state" : {
|
||||
"revision" : "f9acfa1a45f4483fe0f2c434a74e6f68f865d12d",
|
||||
"version" : "0.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-custom-dump",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/swift-custom-dump",
|
||||
"state" : {
|
||||
"revision" : "84b30e1af72e0ffe6dfbfe39d53b8173caacf224",
|
||||
"version" : "0.10.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-dependencies",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/swift-dependencies",
|
||||
"state" : {
|
||||
"revision" : "ad0a6a0dd4d4741263e798f4f5029589c9b5da73",
|
||||
"version" : "0.4.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftui-navigation",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/swiftui-navigation",
|
||||
"state" : {
|
||||
"revision" : "47dd574b900ba5ba679f56ea00d4d282fc7305a6",
|
||||
"version" : "0.7.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "xctest-dynamic-overlay",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
|
||||
"state" : {
|
||||
"revision" : "4af50b38daf0037cfbab15514a241224c3f62f98",
|
||||
"version" : "0.8.5"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1420"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "8DCC021828D512AC007E12D2"
|
||||
BuildableName = "Firezone.app"
|
||||
BlueprintName = "Firezone"
|
||||
ReferencedContainer = "container:Firezone.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = ""
|
||||
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "8DCC022E28D512AE007E12D2"
|
||||
BuildableName = "FirezoneTests.xctest"
|
||||
BlueprintName = "FirezoneTests"
|
||||
ReferencedContainer = "container:Firezone.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "FirezoneKitTests"
|
||||
BuildableName = "FirezoneKitTests"
|
||||
BlueprintName = "FirezoneKitTests"
|
||||
ReferencedContainer = "container:FirezoneKit">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "8DCC021828D512AC007E12D2"
|
||||
BuildableName = "Firezone.app"
|
||||
BlueprintName = "Firezone"
|
||||
ReferencedContainer = "container:Firezone.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "8DCC021828D512AC007E12D2"
|
||||
BuildableName = "Firezone.app"
|
||||
BlueprintName = "Firezone"
|
||||
ReferencedContainer = "container:Firezone.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
50
swift/apple/Firezone/Application/FirezoneApp.swift
Normal file
50
swift/apple/Firezone/Application/FirezoneApp.swift
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// FirezoneApp.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import FirezoneKit
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct FirezoneApp: App {
|
||||
#if os(macOS)
|
||||
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||
#endif
|
||||
|
||||
#if os(iOS)
|
||||
@StateObject var model = AppViewModel()
|
||||
#endif
|
||||
|
||||
var body: some Scene {
|
||||
#if os(iOS)
|
||||
WindowGroup {
|
||||
AppView(model: model)
|
||||
}
|
||||
#else
|
||||
WindowGroup("Settings") {
|
||||
SettingsView(model: appDelegate.settingsViewModel)
|
||||
}
|
||||
.handlesExternalEvents(matching: ["settings"])
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
@MainActor
|
||||
final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
let settingsViewModel = SettingsViewModel()
|
||||
private var menuBar: MenuBar!
|
||||
|
||||
func applicationDidFinishLaunching(_: Notification) {
|
||||
menuBar = MenuBar(settingsViewModel: settingsViewModel)
|
||||
|
||||
// SwiftUI will show the first window group, so close it on launch
|
||||
let window = NSApp.windows[0]
|
||||
window.close()
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_: Notification) {}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "512x512"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
6
swift/apple/Firezone/Assets.xcassets/Contents.json
Normal file
6
swift/apple/Firezone/Assets.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
1
swift/apple/Firezone/Config.xcconfig
Normal file
1
swift/apple/Firezone/Config.xcconfig
Normal file
@@ -0,0 +1 @@
|
||||
#include "Developer.xcconfig"
|
||||
2
swift/apple/Firezone/Developer.xcconfig.ci-iOS
Normal file
2
swift/apple/Firezone/Developer.xcconfig.ci-iOS
Normal file
@@ -0,0 +1,2 @@
|
||||
DEVELOPMENT_TEAM = 0000000000
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.firezone.ios
|
||||
2
swift/apple/Firezone/Developer.xcconfig.ci-macOS
Normal file
2
swift/apple/Firezone/Developer.xcconfig.ci-macOS
Normal file
@@ -0,0 +1,2 @@
|
||||
DEVELOPMENT_TEAM = 0000000000
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.firezone.macos
|
||||
7
swift/apple/Firezone/Developer.xcconfig.template
Normal file
7
swift/apple/Firezone/Developer.xcconfig.template
Normal file
@@ -0,0 +1,7 @@
|
||||
// You Apple developer account's Team ID
|
||||
DEVELOPMENT_TEAM = <team_id>
|
||||
|
||||
// The bundle identifier of the apps.
|
||||
// Should be an app id created at developer.apple.com
|
||||
// with Network Extensions capability.
|
||||
PRODUCT_BUNDLE_IDENTIFIER = <app_id>
|
||||
16
swift/apple/Firezone/Firezone.entitlements
Normal file
16
swift/apple/Firezone/Firezone.entitlements
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict/>
|
||||
</plist>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1" systemVersion="11A491" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="false" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Item" representedClassName="Item" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="timestamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Item" positionX="-63" positionY="-18" width="128" height="44"/>
|
||||
</elements>
|
||||
</model>
|
||||
19
swift/apple/Firezone/Info.plist
Normal file
19
swift/apple/Firezone/Info.plist
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>firezone</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>firezone</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
9
swift/apple/FirezoneKit/.gitignore
vendored
Normal file
9
swift/apple/FirezoneKit/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
||||
86
swift/apple/FirezoneKit/Package.resolved
Normal file
86
swift/apple/FirezoneKit/Package.resolved
Normal file
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "combine-schedulers",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/combine-schedulers",
|
||||
"state" : {
|
||||
"revision" : "882ac01eb7ef9e36d4467eb4b1151e74fcef85ab",
|
||||
"version" : "0.9.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "jwtdecode.swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/auth0/JWTDecode.swift",
|
||||
"state" : {
|
||||
"revision" : "7918a343e674c7707e0be120bb4e21d679be014c",
|
||||
"version" : "3.0.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-case-paths",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/swift-case-paths",
|
||||
"state" : {
|
||||
"revision" : "870133b7b2387df136ad301ec67b2e864b51dda1",
|
||||
"version" : "0.14.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-clocks",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/swift-clocks",
|
||||
"state" : {
|
||||
"revision" : "20b25ca0dd88ebfb9111ec937814ddc5a8880172",
|
||||
"version" : "0.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-custom-dump",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/swift-custom-dump",
|
||||
"state" : {
|
||||
"revision" : "de8ba65649e7ee317b9daf27dd5eebf34bd4be57",
|
||||
"version" : "0.9.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-dependencies",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/swift-dependencies",
|
||||
"state" : {
|
||||
"revision" : "6bb1034e8a1bfbf46dfb766b6c09b7b17e1cba10",
|
||||
"version" : "0.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-log",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-log",
|
||||
"state" : {
|
||||
"revision" : "32e8d724467f8fe623624570367e3d50c5638e46",
|
||||
"version" : "1.5.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftui-navigation",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/swiftui-navigation",
|
||||
"state" : {
|
||||
"revision" : "47dd574b900ba5ba679f56ea00d4d282fc7305a6",
|
||||
"version" : "0.7.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "xctest-dynamic-overlay",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
|
||||
"state" : {
|
||||
"revision" : "ab8c9f45843694dd16be4297e6d44c0634fd9913",
|
||||
"version" : "0.8.4"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
39
swift/apple/FirezoneKit/Package.swift
Normal file
39
swift/apple/FirezoneKit/Package.swift
Normal file
@@ -0,0 +1,39 @@
|
||||
// swift-tools-version: 5.7
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "FirezoneKit",
|
||||
platforms: [.iOS(.v15), .macOS(.v12)],
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to
|
||||
// other packages.
|
||||
.library(name: "FirezoneKit", targets: ["FirezoneKit", "Connlib"]),
|
||||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "0.1.3"),
|
||||
.package(url: "https://github.com/pointfreeco/swiftui-navigation", from: "0.5.0"),
|
||||
.package(url: "https://github.com/auth0/JWTDecode.swift", from: "3.0.0"),
|
||||
],
|
||||
targets: [
|
||||
.binaryTarget(
|
||||
name: "Connlib",
|
||||
path: "../../../rust/connlib/clients/apple/Connlib.xcframework"
|
||||
),
|
||||
.target(
|
||||
name: "FirezoneKit",
|
||||
dependencies: [
|
||||
.product(name: "SwiftUINavigation", package: "swiftui-navigation"),
|
||||
.product(name: "_SwiftUINavigationState", package: "swiftui-navigation"),
|
||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||
.product(name: "JWTDecode", package: "JWTDecode.swift"),
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "FirezoneKitTests",
|
||||
dependencies: ["FirezoneKit"]
|
||||
),
|
||||
]
|
||||
)
|
||||
3
swift/apple/FirezoneKit/README.md
Normal file
3
swift/apple/FirezoneKit/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# FirezoneKit
|
||||
|
||||
A description of this package.
|
||||
@@ -0,0 +1,105 @@
|
||||
//
|
||||
// AuthClient.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import AuthenticationServices
|
||||
import Dependencies
|
||||
import Foundation
|
||||
import JWTDecode
|
||||
|
||||
enum AuthClientError: Error {
|
||||
case invalidCallbackURL(URL?)
|
||||
case jwtDecoderFailure(Error)
|
||||
case sessionFailure(Error)
|
||||
}
|
||||
|
||||
struct AuthClient: Sendable {
|
||||
var signIn: @Sendable (URL) async throws -> Token
|
||||
}
|
||||
|
||||
extension AuthClient: DependencyKey {
|
||||
static var liveValue: AuthClient {
|
||||
let session = WebAuthenticationSession()
|
||||
return AuthClient(
|
||||
signIn: { host in
|
||||
try await session.signIn(host)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension DependencyValues {
|
||||
var auth: AuthClient {
|
||||
get { self[AuthClient.self] }
|
||||
set { self[AuthClient.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
private final class WebAuthenticationSession: NSObject,
|
||||
ASWebAuthenticationPresentationContextProviding
|
||||
{
|
||||
@MainActor
|
||||
func signIn(_ host: URL) async throws -> Token {
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
let callbackURLScheme = "firezone-fd0020211111"
|
||||
let session = ASWebAuthenticationSession(
|
||||
url: host.appendingPathComponent("auth")
|
||||
.appendingQueryItem(URLQueryItem(name: "dest", value: "\(callbackURLScheme)://auth")),
|
||||
callbackURLScheme: callbackURLScheme
|
||||
) { callbackURL, error in
|
||||
if let error {
|
||||
continuation.resume(throwing: AuthClientError.sessionFailure(error))
|
||||
return
|
||||
}
|
||||
|
||||
guard let callbackURL else {
|
||||
continuation.resume(throwing: AuthClientError.invalidCallbackURL(callbackURL))
|
||||
return
|
||||
}
|
||||
|
||||
guard
|
||||
let jwt = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false)?
|
||||
.queryItems?
|
||||
.first(where: { $0.name == "client_auth_token" })?
|
||||
.value
|
||||
else {
|
||||
continuation.resume(throwing: AuthClientError.invalidCallbackURL(callbackURL))
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let token = try Token(portalURL: host, tokenString: jwt)
|
||||
continuation.resume(returning: token)
|
||||
} catch {
|
||||
continuation.resume(throwing: AuthClientError.jwtDecoderFailure(error))
|
||||
}
|
||||
}
|
||||
|
||||
session.presentationContextProvider = self
|
||||
session.prefersEphemeralWebBrowserSession = true
|
||||
|
||||
session.start()
|
||||
}
|
||||
}
|
||||
|
||||
func presentationAnchor(for _: ASWebAuthenticationSession) -> ASPresentationAnchor {
|
||||
ASPresentationAnchor()
|
||||
}
|
||||
}
|
||||
|
||||
extension URL {
|
||||
func appendingQueryItem(_ queryItem: URLQueryItem) -> URL {
|
||||
guard var components = URLComponents(url: self, resolvingAgainstBaseURL: false) else {
|
||||
return self
|
||||
}
|
||||
|
||||
if components.queryItems == nil {
|
||||
components.queryItems = []
|
||||
}
|
||||
|
||||
components.queryItems!.append(queryItem)
|
||||
return components.url ?? self
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// AppView.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import _SwiftUINavigationState
|
||||
import Combine
|
||||
import Dependencies
|
||||
import SwiftUI
|
||||
import SwiftUINavigation
|
||||
|
||||
@MainActor
|
||||
public final class AppViewModel: ObservableObject {
|
||||
@Published var welcomeViewModel: WelcomeViewModel?
|
||||
|
||||
public init() {
|
||||
Task {
|
||||
let tunnel = try await TunnelStore.loadOrCreate()
|
||||
self.welcomeViewModel = WelcomeViewModel(
|
||||
appStore: AppStore(
|
||||
tunnelStore: TunnelStore(
|
||||
tunnel: tunnel
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct AppView: View {
|
||||
@ObservedObject var model: AppViewModel
|
||||
|
||||
public init(model: AppViewModel) {
|
||||
self.model = model
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
public var body: some View {
|
||||
if let model = model.welcomeViewModel {
|
||||
WelcomeView(model: model)
|
||||
} else {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AppView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AppView(model: AppViewModel())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// AuthView.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import AuthenticationServices
|
||||
import Combine
|
||||
import Dependencies
|
||||
import JWTDecode
|
||||
import SwiftUI
|
||||
import XCTestDynamicOverlay
|
||||
|
||||
@MainActor
|
||||
final class AuthViewModel: ObservableObject {
|
||||
@Dependency(\.settingsClient) private var settingsClient
|
||||
@Dependency(\.authStore) private var authStore
|
||||
|
||||
var settingsUndefined: () -> Void = unimplemented("\(AuthViewModel.self).settingsUndefined")
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
func logInButtonTapped() async {
|
||||
guard let portalURL = settingsClient.fetchSettings()?.portalURL else {
|
||||
settingsUndefined()
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try await authStore.signIn(portalURL: portalURL)
|
||||
} catch {
|
||||
dump(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AuthView: View {
|
||||
@ObservedObject var model: AuthViewModel
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Welcome to Firezone").font(.largeTitle)
|
||||
|
||||
Button("Log in") {
|
||||
Task {
|
||||
await model.logInButtonTapped()
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AuthView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AuthView(model: AuthViewModel())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
//
|
||||
// MainView.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Dependencies
|
||||
import NetworkExtension
|
||||
import OSLog
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
final class MainViewModel: ObservableObject {
|
||||
private let logger = Logger.make(for: MainViewModel.self)
|
||||
private var cancellables: Set<AnyCancellable> = []
|
||||
|
||||
private let appStore: AppStore
|
||||
|
||||
var token: Token? {
|
||||
appStore.auth.token
|
||||
}
|
||||
|
||||
var status: NEVPNStatus {
|
||||
appStore.tunnel.status
|
||||
}
|
||||
|
||||
init(appStore: AppStore) {
|
||||
self.appStore = appStore
|
||||
|
||||
appStore.objectWillChange
|
||||
.sink { [weak self] in self?.objectWillChange.send() }
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func signOutButtonTapped() {
|
||||
appStore.auth.signOut()
|
||||
}
|
||||
|
||||
func startTunnel() async {
|
||||
do {
|
||||
if let token = token {
|
||||
try await appStore.tunnel.start(token: token)
|
||||
}
|
||||
} catch {
|
||||
logger.error("Error starting tunnel: \(String(describing: error))")
|
||||
}
|
||||
}
|
||||
|
||||
func stopTunnel() {
|
||||
appStore.tunnel.stop()
|
||||
}
|
||||
}
|
||||
|
||||
struct MainView: View {
|
||||
@ObservedObject var model: MainViewModel
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 56) {
|
||||
VStack {
|
||||
Text("Authenticated").font(.title)
|
||||
Text(model.token?.user ?? "").foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Button("Sign out") {
|
||||
model.signOutButtonTapped()
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .principal) {
|
||||
ConnectionSwitch(
|
||||
status: model.status,
|
||||
connect: { await model.startTunnel() },
|
||||
disconnect: { model.stopTunnel() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MainView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
MainView(
|
||||
model: MainViewModel(
|
||||
appStore: AppStore(
|
||||
tunnelStore: TunnelStore(
|
||||
tunnel: NETunnelProviderManager()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
//
|
||||
// SettingsView.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import Dependencies
|
||||
import SwiftUI
|
||||
import XCTestDynamicOverlay
|
||||
|
||||
public final class SettingsViewModel: ObservableObject {
|
||||
@Dependency(\.settingsClient) private var settingsClient
|
||||
|
||||
@Published var settings: Settings
|
||||
|
||||
public var onSettingsSaved: () -> Void = unimplemented()
|
||||
|
||||
public init() {
|
||||
settings = Settings(portalURL: nil)
|
||||
|
||||
if let storedSettings = settingsClient.fetchSettings() {
|
||||
settings = storedSettings
|
||||
}
|
||||
}
|
||||
|
||||
func saveButtonTapped() {
|
||||
settingsClient.saveSettings(settings)
|
||||
onSettingsSaved()
|
||||
}
|
||||
}
|
||||
|
||||
public struct SettingsView: View {
|
||||
@ObservedObject var model: SettingsViewModel
|
||||
|
||||
public init(model: SettingsViewModel) {
|
||||
self.model = model
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
#if os(iOS)
|
||||
ios
|
||||
#elseif os(macOS)
|
||||
mac
|
||||
#else
|
||||
#error("Unsupported platform")
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
private var ios: some View {
|
||||
NavigationView {
|
||||
form
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if os(macOS)
|
||||
private var mac: some View {
|
||||
form
|
||||
}
|
||||
#endif
|
||||
|
||||
private var form: some View {
|
||||
Form {
|
||||
Section {
|
||||
FormTextField(
|
||||
title: "Portal URL",
|
||||
placeholder: "http://localhost:4567",
|
||||
text: Binding(
|
||||
get: { model.settings.portalURL?.absoluteString ?? "" },
|
||||
set: { model.settings.portalURL = URL(string: $0) }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Settings")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
Button("Save") {
|
||||
model.saveButtonTapped()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FormTextField: View {
|
||||
let title: String
|
||||
let placeholder: String
|
||||
let text: Binding<String>
|
||||
|
||||
var body: some View {
|
||||
#if os(iOS)
|
||||
HStack {
|
||||
Text(title)
|
||||
Spacer()
|
||||
TextField(placeholder, text: text)
|
||||
.autocorrectionDisabled()
|
||||
.multilineTextAlignment(.trailing)
|
||||
.foregroundColor(.secondary)
|
||||
.frame(maxWidth: .infinity)
|
||||
.textInputAutocapitalization(.never)
|
||||
.textContentType(.URL)
|
||||
.keyboardType(.URL)
|
||||
}
|
||||
#else
|
||||
TextField(title, text: text, prompt: Text(placeholder))
|
||||
.autocorrectionDisabled()
|
||||
.multilineTextAlignment(.trailing)
|
||||
.foregroundColor(.secondary)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsView(model: SettingsViewModel())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
//
|
||||
// WelcomeView.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import _SwiftUINavigationState
|
||||
import Combine
|
||||
import Dependencies
|
||||
import SwiftUI
|
||||
import SwiftUINavigation
|
||||
|
||||
@MainActor
|
||||
final class WelcomeViewModel: ObservableObject {
|
||||
@Dependency(\.settingsClient) private var settingsClient
|
||||
@Dependency(\.mainQueue) private var mainQueue
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
enum Destination {
|
||||
case settings(SettingsViewModel)
|
||||
case undefinedSettingsAlert(AlertState<UndefinedSettingsAlertAction>)
|
||||
}
|
||||
|
||||
enum UndefinedSettingsAlertAction {
|
||||
case confirmDefineSettingsButtonTapped
|
||||
}
|
||||
|
||||
enum State {
|
||||
case unauthenticated(AuthViewModel)
|
||||
case authenticated(MainViewModel)
|
||||
}
|
||||
|
||||
@Published var destination: Destination? {
|
||||
didSet {
|
||||
bindDestination()
|
||||
}
|
||||
}
|
||||
|
||||
@Published var state: State? {
|
||||
didSet {
|
||||
bindState()
|
||||
}
|
||||
}
|
||||
|
||||
private let appStore: AppStore
|
||||
|
||||
init(appStore: AppStore) {
|
||||
self.appStore = appStore
|
||||
|
||||
appStore.objectWillChange
|
||||
.receive(on: mainQueue)
|
||||
.sink { [weak self] in self?.objectWillChange.send() }
|
||||
.store(in: &cancellables)
|
||||
|
||||
defer { bindDestination() }
|
||||
|
||||
if settingsClient.fetchSettings()?.portalURL == nil {
|
||||
destination = .undefinedSettingsAlert(.undefinedSettings)
|
||||
}
|
||||
|
||||
appStore.auth.$token
|
||||
.receive(on: mainQueue)
|
||||
.sink(receiveValue: { [weak self] token in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
if token != nil {
|
||||
self.state = .authenticated(MainViewModel(appStore: self.appStore))
|
||||
} else {
|
||||
self.state = .unauthenticated(AuthViewModel())
|
||||
}
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func settingsButtonTapped() {
|
||||
destination = .settings(SettingsViewModel())
|
||||
}
|
||||
|
||||
func handleUndefinedSettingsAlertAction(_ action: UndefinedSettingsAlertAction) {
|
||||
switch action {
|
||||
case .confirmDefineSettingsButtonTapped:
|
||||
destination = .settings(SettingsViewModel())
|
||||
}
|
||||
}
|
||||
|
||||
private func bindDestination() {
|
||||
switch destination {
|
||||
case let .settings(model):
|
||||
model.onSettingsSaved = { [weak self] in
|
||||
self?.destination = nil
|
||||
self?.state = .unauthenticated(AuthViewModel())
|
||||
}
|
||||
|
||||
case .undefinedSettingsAlert, .none:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func bindState() {
|
||||
switch state {
|
||||
case let .unauthenticated(model):
|
||||
model.settingsUndefined = { [weak self] in
|
||||
self?.destination = .undefinedSettingsAlert(.undefinedSettings)
|
||||
}
|
||||
|
||||
case .authenticated, .none:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct WelcomeView: View {
|
||||
@ObservedObject var model: WelcomeViewModel
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Group {
|
||||
switch model.state {
|
||||
case let .unauthenticated(model):
|
||||
AuthView(model: model)
|
||||
case let .authenticated(model):
|
||||
MainView(model: model)
|
||||
case .none:
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
Button {
|
||||
model.settingsButtonTapped()
|
||||
} label: {
|
||||
Label("Settings", systemImage: "gear")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.sheet(unwrapping: $model.destination, case: /WelcomeViewModel.Destination.settings) { $model in
|
||||
SettingsView(model: model)
|
||||
}
|
||||
.alert(
|
||||
unwrapping: $model.destination,
|
||||
case: /WelcomeViewModel.Destination.undefinedSettingsAlert,
|
||||
action: model.handleUndefinedSettingsAlertAction
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct WelcomeView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
WelcomeView(
|
||||
model: WelcomeViewModel(appStore: AppStore(tunnelStore: TunnelStore(tunnel: .init())))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// Logger.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import Dependencies
|
||||
import Foundation
|
||||
import OSLog
|
||||
|
||||
extension Logger {
|
||||
static func make(for type: (some Any).Type) -> Logger {
|
||||
make(category: String(describing: type))
|
||||
}
|
||||
|
||||
static func make(category: String) -> Logger {
|
||||
Logger(subsystem: Bundle.main.bundleIdentifier ?? "dev.firezone.firezone", category: category)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// Optional+Unwrap.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Optional {
|
||||
/// Unwraps `self` or throws error if `none`.
|
||||
/// - Parameter error: Error to thrown in case of nil value.
|
||||
/// - Returns: The wrapped optional value.
|
||||
func unwrap(throwing error: @autoclosure () -> Error) throws -> Wrapped {
|
||||
guard let self else {
|
||||
throw error()
|
||||
}
|
||||
return self
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Keychain+Token.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import JWTDecode
|
||||
|
||||
extension KeychainStorage {
|
||||
static let tokenKey = "token"
|
||||
|
||||
func tokenString() async throws -> String? {
|
||||
let jwt = try await load(KeychainStorage.tokenKey).flatMap { data in
|
||||
String(data: data, encoding: .utf8)
|
||||
}
|
||||
|
||||
guard let jwt else { return nil }
|
||||
return jwt
|
||||
}
|
||||
|
||||
func save(tokenString: String) async throws {
|
||||
try await store(KeychainStorage.tokenKey, tokenString.data(using: .utf8)!)
|
||||
}
|
||||
|
||||
func deleteTokenString() async throws {
|
||||
try await delete(KeychainStorage.tokenKey)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
//
|
||||
// Keychain.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum KeychainError: Error {
|
||||
case securityError(Status)
|
||||
}
|
||||
|
||||
actor Keychain {
|
||||
private static let account = "Firezone"
|
||||
|
||||
func store(key: String, data: Data) throws {
|
||||
let query = ([
|
||||
kSecClass: kSecClassGenericPassword,
|
||||
kSecAttrService: getServiceIdentifier(key),
|
||||
kSecAttrAccount: Keychain.account,
|
||||
kSecValueData: data,
|
||||
] as [CFString: Any]) as CFDictionary
|
||||
|
||||
let status = SecItemAdd(query, nil)
|
||||
|
||||
if status == Status.duplicateItem {
|
||||
try update(key: key, data: data)
|
||||
} else if status != Status.success {
|
||||
throw securityError(status)
|
||||
}
|
||||
}
|
||||
|
||||
func update(key: String, data: Data) throws {
|
||||
let query = ([
|
||||
kSecClass: kSecClassGenericPassword,
|
||||
kSecAttrService: getServiceIdentifier(key),
|
||||
kSecAttrAccount: Keychain.account,
|
||||
] as [CFString: Any]) as CFDictionary
|
||||
|
||||
let updatedData = [kSecValueData: data] as CFDictionary
|
||||
|
||||
let status = SecItemUpdate(query, updatedData)
|
||||
|
||||
if status != Status.success {
|
||||
throw securityError(status)
|
||||
}
|
||||
}
|
||||
|
||||
func load(key: String) throws -> Data? {
|
||||
let query = ([
|
||||
kSecClass: kSecClassGenericPassword,
|
||||
kSecAttrService: getServiceIdentifier(key),
|
||||
kSecAttrAccount: Keychain.account,
|
||||
kSecReturnData: kCFBooleanTrue!,
|
||||
kSecMatchLimit: kSecMatchLimitOne,
|
||||
] as [CFString: Any]) as CFDictionary
|
||||
|
||||
var data: AnyObject?
|
||||
|
||||
let status = SecItemCopyMatching(query, &data)
|
||||
|
||||
if status == Status.success {
|
||||
return data as? Data
|
||||
} else if status == Status.itemNotFound {
|
||||
return nil
|
||||
} else {
|
||||
throw securityError(status)
|
||||
}
|
||||
}
|
||||
|
||||
func delete(key: String) throws {
|
||||
let query = ([
|
||||
kSecClass: kSecClassGenericPassword,
|
||||
kSecAttrService: getServiceIdentifier(key),
|
||||
kSecAttrAccount: Keychain.account,
|
||||
] as [CFString: Any]) as CFDictionary
|
||||
|
||||
let status = SecItemDelete(query)
|
||||
|
||||
if status != Status.success {
|
||||
throw securityError(status)
|
||||
}
|
||||
}
|
||||
|
||||
private func getServiceIdentifier(_ key: String) -> String {
|
||||
var bundleIdentifier = Bundle.main.bundleIdentifier ?? "dev.firezone.firezone"
|
||||
|
||||
if bundleIdentifier.hasSuffix(".network-extension") {
|
||||
bundleIdentifier.removeLast(".network-extension".count)
|
||||
}
|
||||
|
||||
return bundleIdentifier + "." + key
|
||||
}
|
||||
|
||||
private func securityError(_ status: OSStatus) -> Error {
|
||||
KeychainError.securityError(Status(rawValue: status)!)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// KeychainStorage.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import Dependencies
|
||||
import Foundation
|
||||
|
||||
struct KeychainStorage: Sendable {
|
||||
var store: @Sendable (String, Data) async throws -> Void
|
||||
var load: @Sendable (String) async throws -> Data?
|
||||
var delete: @Sendable (String) async throws -> Void
|
||||
}
|
||||
|
||||
extension KeychainStorage: DependencyKey {
|
||||
static var liveValue: KeychainStorage {
|
||||
let keychain = Keychain()
|
||||
|
||||
return KeychainStorage(
|
||||
store: { try await keychain.store(key: $0, data: $1) },
|
||||
load: { try await keychain.load(key: $0) },
|
||||
delete: { try await keychain.delete(key: $0) }
|
||||
)
|
||||
}
|
||||
|
||||
static var testValue: KeychainStorage {
|
||||
let storage = LockIsolated([String: Data]())
|
||||
return KeychainStorage(
|
||||
store: { key, data in
|
||||
storage.withValue {
|
||||
$0[key] = data
|
||||
}
|
||||
},
|
||||
load: { storage.value[$0] },
|
||||
delete: { key in
|
||||
storage.withValue {
|
||||
$0[key] = nil
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension DependencyValues {
|
||||
var keychain: KeychainStorage {
|
||||
get { self[KeychainStorage.self] }
|
||||
set { self[KeychainStorage.self] = newValue }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
//
|
||||
// Status.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
// swiftlint:disable type_body_length
|
||||
// swiftlint:disable file_length
|
||||
// swiftlint:disable identifier_name
|
||||
|
||||
// Copyright (c) 2014 kishikawa katsumi
|
||||
// The MIT License (MIT)
|
||||
// https://github.com/kishikawakatsumi/KeychainAccess
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum Status: OSStatus, Error {
|
||||
case success = 0
|
||||
case unimplemented = -4
|
||||
case diskFull = -34
|
||||
case io = -36
|
||||
case opWr = -49
|
||||
case param = -50
|
||||
case wrPerm = -61
|
||||
case allocate = -108
|
||||
case userCanceled = -128
|
||||
case badReq = -909
|
||||
case internalComponent = -2070
|
||||
case notAvailable = -25291
|
||||
case readOnly = -25292
|
||||
case authFailed = -25293
|
||||
case noSuchKeychain = -25294
|
||||
case invalidKeychain = -25295
|
||||
case duplicateKeychain = -25296
|
||||
case duplicateCallback = -25297
|
||||
case invalidCallback = -25298
|
||||
case duplicateItem = -25299
|
||||
case itemNotFound = -25300
|
||||
case bufferTooSmall = -25301
|
||||
case dataTooLarge = -25302
|
||||
case noSuchAttr = -25303
|
||||
case invalidItemRef = -25304
|
||||
case invalidSearchRef = -25305
|
||||
case noSuchClass = -25306
|
||||
case noDefaultKeychain = -25307
|
||||
case interactionNotAllowed = -25308
|
||||
case readOnlyAttr = -25309
|
||||
case wrongSecVersion = -25310
|
||||
case keySizeNotAllowed = -25311
|
||||
case noStorageModule = -25312
|
||||
case noCertificateModule = -25313
|
||||
case noPolicyModule = -25314
|
||||
case interactionRequired = -25315
|
||||
case dataNotAvailable = -25316
|
||||
case dataNotModifiable = -25317
|
||||
case createChainFailed = -25318
|
||||
case invalidPrefsDomain = -25319
|
||||
case inDarkWake = -25320
|
||||
case aclNotSimple = -25240
|
||||
case policyNotFound = -25241
|
||||
case invalidTrustSetting = -25242
|
||||
case noAccessForItem = -25243
|
||||
case invalidOwnerEdit = -25244
|
||||
case trustNotAvailable = -25245
|
||||
case unsupportedFormat = -25256
|
||||
case unknownFormat = -25257
|
||||
case keyIsSensitive = -25258
|
||||
case multiplePrivKeys = -25259
|
||||
case passphraseRequired = -25260
|
||||
case invalidPasswordRef = -25261
|
||||
case invalidTrustSettings = -25262
|
||||
case noTrustSettings = -25263
|
||||
case pkcs12VerifyFailure = -25264
|
||||
case invalidCertificate = -26265
|
||||
case notSigner = -26267
|
||||
case policyDenied = -26270
|
||||
case invalidKey = -26274
|
||||
case decode = -26275
|
||||
case `internal` = -26276
|
||||
case unsupportedAlgorithm = -26268
|
||||
case unsupportedOperation = -26271
|
||||
case unsupportedPadding = -26273
|
||||
case itemInvalidKey = -34000
|
||||
case itemInvalidKeyType = -34001
|
||||
case itemInvalidValue = -34002
|
||||
case itemClassMissing = -34003
|
||||
case itemMatchUnsupported = -34004
|
||||
case useItemListUnsupported = -34005
|
||||
case useKeychainUnsupported = -34006
|
||||
case useKeychainListUnsupported = -34007
|
||||
case returnDataUnsupported = -34008
|
||||
case returnAttributesUnsupported = -34009
|
||||
case returnRefUnsupported = -34010
|
||||
case returnPersitentRefUnsupported = -34011
|
||||
case valueRefUnsupported = -34012
|
||||
case valuePersistentRefUnsupported = -34013
|
||||
case returnMissingPointer = -34014
|
||||
case matchLimitUnsupported = -34015
|
||||
case itemIllegalQuery = -34016
|
||||
case waitForCallback = -34017
|
||||
case missingEntitlement = -34018
|
||||
case upgradePending = -34019
|
||||
case mpSignatureInvalid = -25327
|
||||
case otrTooOld = -25328
|
||||
case otrIDTooNew = -25329
|
||||
case serviceNotAvailable = -67585
|
||||
case insufficientClientID = -67586
|
||||
case deviceReset = -67587
|
||||
case deviceFailed = -67588
|
||||
case appleAddAppACLSubject = -67589
|
||||
case applePublicKeyIncomplete = -67590
|
||||
case appleSignatureMismatch = -67591
|
||||
case appleInvalidKeyStartDate = -67592
|
||||
case appleInvalidKeyEndDate = -67593
|
||||
case conversionError = -67594
|
||||
case appleSSLv2Rollback = -67595
|
||||
case quotaExceeded = -67596
|
||||
case fileTooBig = -67597
|
||||
case invalidDatabaseBlob = -67598
|
||||
case invalidKeyBlob = -67599
|
||||
case incompatibleDatabaseBlob = -67600
|
||||
case incompatibleKeyBlob = -67601
|
||||
case hostNameMismatch = -67602
|
||||
case unknownCriticalExtensionFlag = -67603
|
||||
case noBasicConstraints = -67604
|
||||
case noBasicConstraintsCA = -67605
|
||||
case invalidAuthorityKeyID = -67606
|
||||
case invalidSubjectKeyID = -67607
|
||||
case invalidKeyUsageForPolicy = -67608
|
||||
case invalidExtendedKeyUsage = -67609
|
||||
case invalidIDLinkage = -67610
|
||||
case pathLengthConstraintExceeded = -67611
|
||||
case invalidRoot = -67612
|
||||
case crlExpired = -67613
|
||||
case crlNotValidYet = -67614
|
||||
case crlNotFound = -67615
|
||||
case crlServerDown = -67616
|
||||
case crlBadURI = -67617
|
||||
case unknownCertExtension = -67618
|
||||
case unknownCRLExtension = -67619
|
||||
case crlNotTrusted = -67620
|
||||
case crlPolicyFailed = -67621
|
||||
case idpFailure = -67622
|
||||
case smimeEmailAddressesNotFound = -67623
|
||||
case smimeBadExtendedKeyUsage = -67624
|
||||
case smimeBadKeyUsage = -67625
|
||||
case smimeKeyUsageNotCritical = -67626
|
||||
case smimeNoEmailAddress = -67627
|
||||
case smimeSubjAltNameNotCritical = -67628
|
||||
case sslBadExtendedKeyUsage = -67629
|
||||
case ocspBadResponse = -67630
|
||||
case ocspBadRequest = -67631
|
||||
case ocspUnavailable = -67632
|
||||
case ocspStatusUnrecognized = -67633
|
||||
case endOfData = -67634
|
||||
case incompleteCertRevocationCheck = -67635
|
||||
case networkFailure = -67636
|
||||
case ocspNotTrustedToAnchor = -67637
|
||||
case recordModified = -67638
|
||||
case ocspSignatureError = -67639
|
||||
case ocspNoSigner = -67640
|
||||
case ocspResponderMalformedReq = -67641
|
||||
case ocspResponderInternalError = -67642
|
||||
case ocspResponderTryLater = -67643
|
||||
case ocspResponderSignatureRequired = -67644
|
||||
case ocspResponderUnauthorized = -67645
|
||||
case ocspResponseNonceMismatch = -67646
|
||||
case codeSigningBadCertChainLength = -67647
|
||||
case codeSigningNoBasicConstraints = -67648
|
||||
case codeSigningBadPathLengthConstraint = -67649
|
||||
case codeSigningNoExtendedKeyUsage = -67650
|
||||
case codeSigningDevelopment = -67651
|
||||
case resourceSignBadCertChainLength = -67652
|
||||
case resourceSignBadExtKeyUsage = -67653
|
||||
case trustSettingDeny = -67654
|
||||
case invalidSubjectName = -67655
|
||||
case unknownQualifiedCertStatement = -67656
|
||||
case mobileMeRequestQueued = -67657
|
||||
case mobileMeRequestRedirected = -67658
|
||||
case mobileMeServerError = -67659
|
||||
case mobileMeServerNotAvailable = -67660
|
||||
case mobileMeServerAlreadyExists = -67661
|
||||
case mobileMeServerServiceErr = -67662
|
||||
case mobileMeRequestAlreadyPending = -67663
|
||||
case mobileMeNoRequestPending = -67664
|
||||
case mobileMeCSRVerifyFailure = -67665
|
||||
case mobileMeFailedConsistencyCheck = -67666
|
||||
case notInitialized = -67667
|
||||
case invalidHandleUsage = -67668
|
||||
case pvcReferentNotFound = -67669
|
||||
case functionIntegrityFail = -67670
|
||||
case internalError = -67671
|
||||
case memoryError = -67672
|
||||
case invalidData = -67673
|
||||
case mdsError = -67674
|
||||
case invalidPointer = -67675
|
||||
case selfCheckFailed = -67676
|
||||
case functionFailed = -67677
|
||||
case moduleManifestVerifyFailed = -67678
|
||||
case invalidGUID = -67679
|
||||
case invalidHandle = -67680
|
||||
case invalidDBList = -67681
|
||||
case invalidPassthroughID = -67682
|
||||
case invalidNetworkAddress = -67683
|
||||
case crlAlreadySigned = -67684
|
||||
case invalidNumberOfFields = -67685
|
||||
case verificationFailure = -67686
|
||||
case unknownTag = -67687
|
||||
case invalidSignature = -67688
|
||||
case invalidName = -67689
|
||||
case invalidCertificateRef = -67690
|
||||
case invalidCertificateGroup = -67691
|
||||
case tagNotFound = -67692
|
||||
case invalidQuery = -67693
|
||||
case invalidValue = -67694
|
||||
case callbackFailed = -67695
|
||||
case aclDeleteFailed = -67696
|
||||
case aclReplaceFailed = -67697
|
||||
case aclAddFailed = -67698
|
||||
case aclChangeFailed = -67699
|
||||
case invalidAccessCredentials = -67700
|
||||
case invalidRecord = -67701
|
||||
case invalidACL = -67702
|
||||
case invalidSampleValue = -67703
|
||||
case incompatibleVersion = -67704
|
||||
case privilegeNotGranted = -67705
|
||||
case invalidScope = -67706
|
||||
case pvcAlreadyConfigured = -67707
|
||||
case invalidPVC = -67708
|
||||
case emmLoadFailed = -67709
|
||||
case emmUnloadFailed = -67710
|
||||
case addinLoadFailed = -67711
|
||||
case invalidKeyRef = -67712
|
||||
case invalidKeyHierarchy = -67713
|
||||
case addinUnloadFailed = -67714
|
||||
case libraryReferenceNotFound = -67715
|
||||
case invalidAddinFunctionTable = -67716
|
||||
case invalidServiceMask = -67717
|
||||
case moduleNotLoaded = -67718
|
||||
case invalidSubServiceID = -67719
|
||||
case attributeNotInContext = -67720
|
||||
case moduleManagerInitializeFailed = -67721
|
||||
case moduleManagerNotFound = -67722
|
||||
case eventNotificationCallbackNotFound = -67723
|
||||
case inputLengthError = -67724
|
||||
case outputLengthError = -67725
|
||||
case privilegeNotSupported = -67726
|
||||
case deviceError = -67727
|
||||
case attachHandleBusy = -67728
|
||||
case notLoggedIn = -67729
|
||||
case algorithmMismatch = -67730
|
||||
case keyUsageIncorrect = -67731
|
||||
case keyBlobTypeIncorrect = -67732
|
||||
case keyHeaderInconsistent = -67733
|
||||
case unsupportedKeyFormat = -67734
|
||||
case unsupportedKeySize = -67735
|
||||
case invalidKeyUsageMask = -67736
|
||||
case unsupportedKeyUsageMask = -67737
|
||||
case invalidKeyAttributeMask = -67738
|
||||
case unsupportedKeyAttributeMask = -67739
|
||||
case invalidKeyLabel = -67740
|
||||
case unsupportedKeyLabel = -67741
|
||||
case invalidKeyFormat = -67742
|
||||
case unsupportedVectorOfBuffers = -67743
|
||||
case invalidInputVector = -67744
|
||||
case invalidOutputVector = -67745
|
||||
case invalidContext = -67746
|
||||
case invalidAlgorithm = -67747
|
||||
case invalidAttributeKey = -67748
|
||||
case missingAttributeKey = -67749
|
||||
case invalidAttributeInitVector = -67750
|
||||
case missingAttributeInitVector = -67751
|
||||
case invalidAttributeSalt = -67752
|
||||
case missingAttributeSalt = -67753
|
||||
case invalidAttributePadding = -67754
|
||||
case missingAttributePadding = -67755
|
||||
case invalidAttributeRandom = -67756
|
||||
case missingAttributeRandom = -67757
|
||||
case invalidAttributeSeed = -67758
|
||||
case missingAttributeSeed = -67759
|
||||
case invalidAttributePassphrase = -67760
|
||||
case missingAttributePassphrase = -67761
|
||||
case invalidAttributeKeyLength = -67762
|
||||
case missingAttributeKeyLength = -67763
|
||||
case invalidAttributeBlockSize = -67764
|
||||
case missingAttributeBlockSize = -67765
|
||||
case invalidAttributeOutputSize = -67766
|
||||
case missingAttributeOutputSize = -67767
|
||||
case invalidAttributeRounds = -67768
|
||||
case missingAttributeRounds = -67769
|
||||
case invalidAlgorithmParms = -67770
|
||||
case missingAlgorithmParms = -67771
|
||||
case invalidAttributeLabel = -67772
|
||||
case missingAttributeLabel = -67773
|
||||
case invalidAttributeKeyType = -67774
|
||||
case missingAttributeKeyType = -67775
|
||||
case invalidAttributeMode = -67776
|
||||
case missingAttributeMode = -67777
|
||||
case invalidAttributeEffectiveBits = -67778
|
||||
case missingAttributeEffectiveBits = -67779
|
||||
case invalidAttributeStartDate = -67780
|
||||
case missingAttributeStartDate = -67781
|
||||
case invalidAttributeEndDate = -67782
|
||||
case missingAttributeEndDate = -67783
|
||||
case invalidAttributeVersion = -67784
|
||||
case missingAttributeVersion = -67785
|
||||
case invalidAttributePrime = -67786
|
||||
case missingAttributePrime = -67787
|
||||
case invalidAttributeBase = -67788
|
||||
case missingAttributeBase = -67789
|
||||
case invalidAttributeSubprime = -67790
|
||||
case missingAttributeSubprime = -67791
|
||||
case invalidAttributeIterationCount = -67792
|
||||
case missingAttributeIterationCount = -67793
|
||||
case invalidAttributeDLDBHandle = -67794
|
||||
case missingAttributeDLDBHandle = -67795
|
||||
case invalidAttributeAccessCredentials = -67796
|
||||
case missingAttributeAccessCredentials = -67797
|
||||
case invalidAttributePublicKeyFormat = -67798
|
||||
case missingAttributePublicKeyFormat = -67799
|
||||
case invalidAttributePrivateKeyFormat = -67800
|
||||
case missingAttributePrivateKeyFormat = -67801
|
||||
case invalidAttributeSymmetricKeyFormat = -67802
|
||||
case missingAttributeSymmetricKeyFormat = -67803
|
||||
case invalidAttributeWrappedKeyFormat = -67804
|
||||
case missingAttributeWrappedKeyFormat = -67805
|
||||
case stagedOperationInProgress = -67806
|
||||
case stagedOperationNotStarted = -67807
|
||||
case verifyFailed = -67808
|
||||
case querySizeUnknown = -67809
|
||||
case blockSizeMismatch = -67810
|
||||
case publicKeyInconsistent = -67811
|
||||
case deviceVerifyFailed = -67812
|
||||
case invalidLoginName = -67813
|
||||
case alreadyLoggedIn = -67814
|
||||
case invalidDigestAlgorithm = -67815
|
||||
case invalidCRLGroup = -67816
|
||||
case certificateCannotOperate = -67817
|
||||
case certificateExpired = -67818
|
||||
case certificateNotValidYet = -67819
|
||||
case certificateRevoked = -67820
|
||||
case certificateSuspended = -67821
|
||||
case insufficientCredentials = -67822
|
||||
case invalidAction = -67823
|
||||
case invalidAuthority = -67824
|
||||
case verifyActionFailed = -67825
|
||||
case invalidCertAuthority = -67826
|
||||
case invaldCRLAuthority = -67827
|
||||
case invalidCRLEncoding = -67828
|
||||
case invalidCRLType = -67829
|
||||
case invalidCRL = -67830
|
||||
case invalidFormType = -67831
|
||||
case invalidID = -67832
|
||||
case invalidIdentifier = -67833
|
||||
case invalidIndex = -67834
|
||||
case invalidPolicyIdentifiers = -67835
|
||||
case invalidTimeString = -67836
|
||||
case invalidReason = -67837
|
||||
case invalidRequestInputs = -67838
|
||||
case invalidResponseVector = -67839
|
||||
case invalidStopOnPolicy = -67840
|
||||
case invalidTuple = -67841
|
||||
case multipleValuesUnsupported = -67842
|
||||
case notTrusted = -67843
|
||||
case noDefaultAuthority = -67844
|
||||
case rejectedForm = -67845
|
||||
case requestLost = -67846
|
||||
case requestRejected = -67847
|
||||
case unsupportedAddressType = -67848
|
||||
case unsupportedService = -67849
|
||||
case invalidTupleGroup = -67850
|
||||
case invalidBaseACLs = -67851
|
||||
case invalidTupleCredendtials = -67852
|
||||
case invalidEncoding = -67853
|
||||
case invalidValidityPeriod = -67854
|
||||
case invalidRequestor = -67855
|
||||
case requestDescriptor = -67856
|
||||
case invalidBundleInfo = -67857
|
||||
case invalidCRLIndex = -67858
|
||||
case noFieldValues = -67859
|
||||
case unsupportedFieldFormat = -67860
|
||||
case unsupportedIndexInfo = -67861
|
||||
case unsupportedLocality = -67862
|
||||
case unsupportedNumAttributes = -67863
|
||||
case unsupportedNumIndexes = -67864
|
||||
case unsupportedNumRecordTypes = -67865
|
||||
case fieldSpecifiedMultiple = -67866
|
||||
case incompatibleFieldFormat = -67867
|
||||
case invalidParsingModule = -67868
|
||||
case databaseLocked = -67869
|
||||
case datastoreIsOpen = -67870
|
||||
case missingValue = -67871
|
||||
case unsupportedQueryLimits = -67872
|
||||
case unsupportedNumSelectionPreds = -67873
|
||||
case unsupportedOperator = -67874
|
||||
case invalidDBLocation = -67875
|
||||
case invalidAccessRequest = -67876
|
||||
case invalidIndexInfo = -67877
|
||||
case invalidNewOwner = -67878
|
||||
case invalidModifyMode = -67879
|
||||
case missingRequiredExtension = -67880
|
||||
case extendedKeyUsageNotCritical = -67881
|
||||
case timestampMissing = -67882
|
||||
case timestampInvalid = -67883
|
||||
case timestampNotTrusted = -67884
|
||||
case timestampServiceNotAvailable = -67885
|
||||
case timestampBadAlg = -67886
|
||||
case timestampBadRequest = -67887
|
||||
case timestampBadDataFormat = -67888
|
||||
case timestampTimeNotAvailable = -67889
|
||||
case timestampUnacceptedPolicy = -67890
|
||||
case timestampUnacceptedExtension = -67891
|
||||
case timestampAddInfoNotAvailable = -67892
|
||||
case timestampSystemFailure = -67893
|
||||
case signingTimeMissing = -67894
|
||||
case timestampRejection = -67895
|
||||
case timestampWaiting = -67896
|
||||
case timestampRevocationWarning = -67897
|
||||
case timestampRevocationNotification = -67898
|
||||
case unexpectedError = -99999
|
||||
}
|
||||
|
||||
extension Status {
|
||||
static func == (lhs: OSStatus, rhs: Status) -> Bool {
|
||||
lhs == rhs.rawValue
|
||||
}
|
||||
|
||||
static func != (lhs: OSStatus, rhs: Status) -> Bool {
|
||||
lhs != rhs.rawValue
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
//
|
||||
// FirezoneError.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum FirezoneError: Error {
|
||||
case missingPortalURL
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// Token.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import JWTDecode
|
||||
|
||||
struct Token {
|
||||
// The user associated with this token parsed from the jwt sub claim.
|
||||
var user: String? { jwt["sub"].string }
|
||||
|
||||
// The VPN session duration parsed from the jwt exp claim.
|
||||
var expiresAt: Date? { jwt["exp"].date }
|
||||
|
||||
// A convenience property to check if the token has expired.
|
||||
var expired: Bool { jwt.expired }
|
||||
|
||||
// The base64 encoded jwt string.
|
||||
var string: String { jwt.string }
|
||||
|
||||
// The portal URL
|
||||
let portalURL: URL
|
||||
|
||||
// The decoded jwt.
|
||||
private let jwt: JWT
|
||||
|
||||
init(portalURL: URL, tokenString: String) throws {
|
||||
self.portalURL = portalURL
|
||||
self.jwt = try decode(jwt: tokenString)
|
||||
}
|
||||
}
|
||||
|
||||
extension Token: Hashable {
|
||||
static func == (lhs: Token, rhs: Token) -> Bool {
|
||||
lhs.string == rhs.string
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
string.hash(into: &hasher)
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
extension Token {
|
||||
static let expired =
|
||||
try! Token(
|
||||
portalURL: URL(string: "http://localhost:4568")!,
|
||||
tokenString: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0QGZpcmV6b25lLmRldiIsImV4cCI6MTY2NzE0MDU1NX0.YLLQyfX6AlHQb90AMRrBTbvBuRxBzVYe0YwohfcD0r7KdBmR5Y-AcP0eYVC2DK-MSSJVjzs2j7SMPCvwJRc1z0LX_U4PkHjL5HPUIb3_rE1MIP8Hn8Ng5mk6SaTj6EJm3qTmm44bPiy21kntcqp-b9CSFqwc1IQHVHXnbcqcv4sVit2sTXJSNvNRRtO8ZTsC007T9skYBGVfCI-kSFyxQe9CoPQxYzFF8KKtCqmmT-t5g0et78IcwToOYeCxc0zOe14OQFadDZabmvJ_xfvC4iRKPfbOyQfNQqIQ_xh3iaGry2iSD4yMALKvgA7Ij4Ixz0GEnyqEfvOeCRA1UoFcLg"
|
||||
)
|
||||
|
||||
static let valid =
|
||||
try! Token(
|
||||
portalURL: URL(string: "http://localhost:4568")!,
|
||||
tokenString: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0QGZpcmV6b25lLmRldiIsImV4cCI6MTY2OTk0MDU1NX0.cLkLdIUBF5FH9e32yBPcfOXun11iYhtbxsVdqlZt3U5J-CYfBNikg5jbG6N7h2BYCBjScnmpu7G249la-lahO7IZWM3qil4NNyhKMZxzA_3cgC3362MBX-7xmlpxjR61b_yE1wOfGjjm_xOKIUTfkkfyTGxvQkXecdbOgpZ7WV4PkG7QD-JHgaQvIQCMbQuC5-d225z6rC43itiRxkq5mRet1d6N5VPxq1tdOD4N7mMs9I1NjJpFGcmJw8r8tlik8r7oxlJpmZN6aw4wcHV3gMd_Dmm-ZYW8M6aVvOpNIabi4hvB0Snqx-w4SqHy1stjfm-vHiGfusucX4G4igk4Qw"
|
||||
)
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
//
|
||||
// Settings.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct Settings: Codable, Hashable {
|
||||
var portalURL: URL?
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// SettingsClient.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import Dependencies
|
||||
import Foundation
|
||||
|
||||
struct SettingsClient {
|
||||
var fetchSettings: () -> Settings?
|
||||
var saveSettings: (Settings?) -> Void
|
||||
}
|
||||
|
||||
extension SettingsClient: DependencyKey {
|
||||
static let liveValue = SettingsClient(
|
||||
fetchSettings: {
|
||||
guard let data = UserDefaults.standard.data(forKey: "settings") else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return try? JSONDecoder().decode(Settings.self, from: data)
|
||||
},
|
||||
saveSettings: { settings in
|
||||
let data = try? JSONEncoder().encode(settings)
|
||||
UserDefaults.standard.set(data, forKey: "settings")
|
||||
}
|
||||
)
|
||||
|
||||
static var testValue: SettingsClient {
|
||||
let settings = LockIsolated(Settings?.none)
|
||||
return SettingsClient(
|
||||
fetchSettings: { settings.value },
|
||||
saveSettings: { settings.setValue($0) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension DependencyValues {
|
||||
var settingsClient: SettingsClient {
|
||||
get { self[SettingsClient.self] }
|
||||
set { self[SettingsClient.self] = newValue }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// AppStore.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Dependencies
|
||||
import OSLog
|
||||
|
||||
@MainActor
|
||||
final class AppStore: ObservableObject {
|
||||
private let logger = Logger.make(for: AppStore.self)
|
||||
|
||||
@Dependency(\.authStore) var auth
|
||||
@Dependency(\.mainQueue) var mainQueue
|
||||
|
||||
let tunnel: TunnelStore
|
||||
private var cancellables: Set<AnyCancellable> = []
|
||||
|
||||
init(tunnelStore: TunnelStore) {
|
||||
tunnel = tunnelStore
|
||||
|
||||
Publishers.Merge(
|
||||
auth.objectWillChange,
|
||||
tunnel.objectWillChange
|
||||
)
|
||||
.receive(on: mainQueue)
|
||||
.sink { [weak self] in
|
||||
self?.objectWillChange.send()
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
auth.$token
|
||||
.receive(on: mainQueue)
|
||||
.sink { [weak self] token in
|
||||
Task { [weak self] in
|
||||
await self?.handleTokenChanged(token)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
private func handleTokenChanged(_ token: Token?) async {
|
||||
if let token = token {
|
||||
do {
|
||||
try await tunnel.start(token: token)
|
||||
} catch {
|
||||
logger.error("Error starting tunnel: \(String(describing: error))")
|
||||
}
|
||||
} else {
|
||||
tunnel.stop()
|
||||
}
|
||||
}
|
||||
|
||||
private func signOutAndStopTunnel() {
|
||||
tunnel.stop()
|
||||
auth.signOut()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
//
|
||||
// AuthStore.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Dependencies
|
||||
import Foundation
|
||||
import OSLog
|
||||
|
||||
extension AuthStore: DependencyKey {
|
||||
static var liveValue: AuthStore = .shared
|
||||
}
|
||||
|
||||
extension DependencyValues {
|
||||
var authStore: AuthStore {
|
||||
get { self[AuthStore.self] }
|
||||
set { self[AuthStore.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class AuthStore: ObservableObject {
|
||||
private let logger = Logger.make(for: AuthStore.self)
|
||||
|
||||
static let shared = AuthStore()
|
||||
|
||||
@Dependency(\.keychain) private var keychain
|
||||
@Dependency(\.auth) private var auth
|
||||
@Dependency(\.settingsClient) private var settingsClient
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
@Published private(set) var token: Token?
|
||||
|
||||
private init() {
|
||||
Task {
|
||||
self.token = await {
|
||||
guard let portalURL = settingsClient.fetchSettings()?.portalURL else {
|
||||
logger.debug("No portal URL found in settings")
|
||||
return nil
|
||||
}
|
||||
guard let tokenString = try? await keychain.tokenString() else {
|
||||
logger.debug("Token string not found in keychain")
|
||||
return nil
|
||||
}
|
||||
guard let token = try? Token(portalURL: portalURL, tokenString: tokenString) else {
|
||||
logger.debug("Token string recovered from keychain is invalid")
|
||||
return nil
|
||||
}
|
||||
logger.debug("Token recovered from keychain.")
|
||||
return token
|
||||
}()
|
||||
}
|
||||
|
||||
$token.dropFirst()
|
||||
.sink { [weak self] token in
|
||||
Task { [weak self] in
|
||||
if let token {
|
||||
try? await self?.keychain.save(tokenString: token.string)
|
||||
self?.logger.debug("token saved on keychain.")
|
||||
} else {
|
||||
try? await self?.keychain.deleteTokenString()
|
||||
self?.logger.debug("token deleted from keychain.")
|
||||
}
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func signIn(portalURL: URL) async throws {
|
||||
logger.trace("\(#function)")
|
||||
|
||||
let token = try await auth.signIn(portalURL)
|
||||
self.token = token
|
||||
}
|
||||
|
||||
func signIn() async throws {
|
||||
logger.trace("\(#function)")
|
||||
|
||||
let portalURL = try settingsClient.fetchSettings().flatMap(\.portalURL)
|
||||
.unwrap(throwing: FirezoneError.missingPortalURL)
|
||||
try await signIn(portalURL: portalURL)
|
||||
}
|
||||
|
||||
func signOut() {
|
||||
logger.trace("\(#function)")
|
||||
|
||||
token = nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
//
|
||||
// TunnelStore.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
import OSLog
|
||||
|
||||
// TODO: Can this file be removed since we're managing the tunnel in connlib?
|
||||
|
||||
@MainActor
|
||||
final class TunnelStore: ObservableObject {
|
||||
private static let logger = Logger.make(for: TunnelStore.self)
|
||||
|
||||
var tunnel: NETunnelProviderManager {
|
||||
didSet { setupTunnelObservers() }
|
||||
}
|
||||
|
||||
@Published private(set) var status: NEVPNStatus = .invalid {
|
||||
didSet { TunnelStore.logger.info("status changed: \(self.status.description)") }
|
||||
}
|
||||
|
||||
@Published private(set) var isEnabled = false {
|
||||
didSet { TunnelStore.logger.info("isEnabled changed: \(self.isEnabled.description)") }
|
||||
}
|
||||
|
||||
private var tunnelObservingTasks: [Task<Void, Never>] = []
|
||||
|
||||
init(tunnel: NETunnelProviderManager) {
|
||||
self.tunnel = tunnel
|
||||
tunnel.isEnabled = true
|
||||
setupTunnelObservers()
|
||||
}
|
||||
|
||||
static func loadOrCreate() async throws -> NETunnelProviderManager {
|
||||
logger.trace("\(#function)")
|
||||
|
||||
let managers = try await NETunnelProviderManager.loadAllFromPreferences()
|
||||
|
||||
if let tunnel = managers.first {
|
||||
return tunnel
|
||||
}
|
||||
|
||||
let tunnel = makeManager()
|
||||
try await tunnel.saveToPreferences()
|
||||
try await tunnel.loadFromPreferences()
|
||||
|
||||
return tunnel
|
||||
}
|
||||
|
||||
func start(token: Token) async throws {
|
||||
TunnelStore.logger.trace("\(#function)")
|
||||
|
||||
// make sure we have latest preferences before starting
|
||||
try await tunnel.loadFromPreferences()
|
||||
|
||||
tunnel.protocolConfiguration = Self.makeProtocolConfiguration(token: token)
|
||||
tunnel.isEnabled = true
|
||||
try await tunnel.saveToPreferences()
|
||||
|
||||
let session = tunnel.connection as! NETunnelProviderSession
|
||||
try session.startTunnel()
|
||||
}
|
||||
|
||||
func stop() {
|
||||
TunnelStore.logger.trace("\(#function)")
|
||||
let session = tunnel.connection as! NETunnelProviderSession
|
||||
session.stopTunnel()
|
||||
}
|
||||
|
||||
private static func makeManager() -> NETunnelProviderManager {
|
||||
logger.trace("\(#function)")
|
||||
|
||||
let manager = NETunnelProviderManager()
|
||||
manager.localizedDescription = "Firezone"
|
||||
|
||||
let proto = makeProtocolConfiguration()
|
||||
manager.protocolConfiguration = proto
|
||||
manager.isEnabled = true
|
||||
|
||||
return manager
|
||||
}
|
||||
|
||||
private static func makeProtocolConfiguration(token: Token? = nil) -> NETunnelProviderProtocol {
|
||||
let proto = NETunnelProviderProtocol()
|
||||
|
||||
proto.providerBundleIdentifier = Bundle.main.bundleIdentifier.map {
|
||||
"\($0).network-extension"
|
||||
}
|
||||
if let token = token {
|
||||
proto.providerConfiguration = [
|
||||
"portalURL": token.portalURL.absoluteString,
|
||||
"token": token.string
|
||||
]
|
||||
}
|
||||
proto.serverAddress = "Firezone addresses"
|
||||
return proto
|
||||
}
|
||||
|
||||
private func setupTunnelObservers() {
|
||||
TunnelStore.logger.trace("\(#function)")
|
||||
|
||||
tunnelObservingTasks.forEach { $0.cancel() }
|
||||
tunnelObservingTasks.removeAll()
|
||||
|
||||
tunnelObservingTasks.append(
|
||||
Task {
|
||||
for await notification in NotificationCenter.default.notifications(
|
||||
named: .NEVPNStatusDidChange,
|
||||
object: nil
|
||||
) {
|
||||
guard let session = notification.object as? NETunnelProviderSession,
|
||||
let tunnelProvider = session.manager as? NETunnelProviderManager
|
||||
else {
|
||||
return
|
||||
}
|
||||
self.status = tunnelProvider.connection.status
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
func removeProfile() async throws {
|
||||
TunnelStore.logger.trace("\(#function)")
|
||||
|
||||
try await tunnel.removeFromPreferences()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Extensions
|
||||
|
||||
/// Make NEVPNStatus convertible to a string
|
||||
extension NEVPNStatus: CustomStringConvertible {
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .disconnected: return "Disconnected"
|
||||
case .invalid: return "Invalid"
|
||||
case .connected: return "Connected"
|
||||
case .connecting: return "Connecting"
|
||||
case .disconnecting: return "Disconnecting"
|
||||
case .reasserting: return "Reconnecting"
|
||||
@unknown default: return "Unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// Alerts.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import _SwiftUINavigationState
|
||||
|
||||
extension AlertState where Action == WelcomeViewModel.UndefinedSettingsAlertAction {
|
||||
static let undefinedSettings = AlertState(
|
||||
title: TextState("No settings found."),
|
||||
message: TextState("To sign in, you first need to configure portal settings."),
|
||||
dismissButton: .default(
|
||||
TextState("Define settings"),
|
||||
action: .send(.confirmDefineSettingsButtonTapped)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// ConnectionSwitch.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import NetworkExtension
|
||||
import SwiftUI
|
||||
|
||||
struct ConnectionSwitch: View {
|
||||
let status: NEVPNStatus
|
||||
var connect: () async -> Void
|
||||
var disconnect: () async -> Void
|
||||
|
||||
@State private var isInFlight = false
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
ZStack {
|
||||
Toggle(
|
||||
"", isOn: .init(
|
||||
get: { status == .connected },
|
||||
set: { isOn in
|
||||
Task {
|
||||
isInFlight = true
|
||||
defer { isInFlight = false }
|
||||
|
||||
if isOn {
|
||||
await connect()
|
||||
} else {
|
||||
await disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
.labelsHidden()
|
||||
.toggleStyle(.switch)
|
||||
.opacity(isInFlight ? 0 : 1)
|
||||
|
||||
if isInFlight {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
|
||||
Text(status.description).frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ConnectionSwitch_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ConnectionSwitch(status: .connected, connect: {}, disconnect: {})
|
||||
}
|
||||
}
|
||||
202
swift/apple/FirezoneKit/Sources/FirezoneKit/Views/MenuBar.swift
Normal file
202
swift/apple/FirezoneKit/Sources/FirezoneKit/Views/MenuBar.swift
Normal file
@@ -0,0 +1,202 @@
|
||||
//
|
||||
// MenuBar.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
// swiftlint:disable function_parameter_count
|
||||
|
||||
#if os(macOS)
|
||||
import Combine
|
||||
import Dependencies
|
||||
import OSLog
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
public final class MenuBar {
|
||||
let logger = Logger.make(for: MenuBar.self)
|
||||
@Dependency(\.mainQueue) private var mainQueue
|
||||
|
||||
private var appStore: AppStore? {
|
||||
didSet {
|
||||
setupObservers()
|
||||
}
|
||||
}
|
||||
|
||||
private var cancellables: Set<AnyCancellable> = []
|
||||
private var statusItem: NSStatusItem
|
||||
|
||||
let settingsViewModel: SettingsViewModel
|
||||
|
||||
public init(settingsViewModel: SettingsViewModel) {
|
||||
self.settingsViewModel = settingsViewModel
|
||||
|
||||
settingsViewModel.onSettingsSaved = {
|
||||
// TODO: close settings window and sign in
|
||||
}
|
||||
|
||||
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||||
|
||||
if let button = statusItem.button {
|
||||
button.image = NSImage(
|
||||
// TODO: Replace with AppIcon when it exists
|
||||
systemSymbolName: "circle",
|
||||
accessibilityDescription: "Firezone icon"
|
||||
)
|
||||
}
|
||||
|
||||
createMenu()
|
||||
|
||||
Task {
|
||||
let tunnel = try await TunnelStore.loadOrCreate()
|
||||
self.appStore = AppStore(tunnelStore: TunnelStore(tunnel: tunnel))
|
||||
}
|
||||
}
|
||||
|
||||
private func setupObservers() {
|
||||
appStore?.auth.$token
|
||||
.receive(on: mainQueue)
|
||||
.sink { [weak self] token in
|
||||
if let token {
|
||||
self?.showLoggedIn(token.user)
|
||||
} else {
|
||||
self?.showLoggedOut()
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
appStore?.tunnel.$status
|
||||
.receive(on: mainQueue)
|
||||
.sink { [weak self] status in
|
||||
if status == .connected {
|
||||
self?.connectionMenuItem.title = "Disconnect"
|
||||
} else {
|
||||
self?.connectionMenuItem.title = "Connect"
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
private lazy var menu = NSMenu()
|
||||
|
||||
private lazy var connectionMenuItem = createMenuItem(
|
||||
menu,
|
||||
title: "Connect",
|
||||
action: #selector(connectButtonTapped),
|
||||
target: self
|
||||
)
|
||||
|
||||
private lazy var loginMenuItem = createMenuItem(
|
||||
menu,
|
||||
title: "Login",
|
||||
action: #selector(loginButtonTapped),
|
||||
target: self
|
||||
)
|
||||
private lazy var logoutMenuItem = createMenuItem(
|
||||
menu,
|
||||
title: "Logout",
|
||||
action: #selector(logoutButtonTapped),
|
||||
isHidden: true,
|
||||
target: self
|
||||
)
|
||||
private lazy var settingsMenuItem = createMenuItem(
|
||||
menu,
|
||||
title: "Settings",
|
||||
action: #selector(settingsButtonTapped),
|
||||
target: self
|
||||
)
|
||||
private lazy var quitMenuItem = createMenuItem(
|
||||
menu,
|
||||
title: "Quit",
|
||||
action: #selector(NSApplication.terminate(_:)),
|
||||
key: "q",
|
||||
target: nil
|
||||
)
|
||||
|
||||
private func createMenu() {
|
||||
menu.addItem(connectionMenuItem)
|
||||
menu.addItem(loginMenuItem)
|
||||
menu.addItem(logoutMenuItem)
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
menu.addItem(settingsMenuItem)
|
||||
menu.addItem(quitMenuItem)
|
||||
|
||||
statusItem.menu = menu
|
||||
}
|
||||
|
||||
private func createMenuItem(
|
||||
_: NSMenu,
|
||||
title: String,
|
||||
action: Selector,
|
||||
isHidden: Bool = false,
|
||||
key: String = "",
|
||||
target: AnyObject?
|
||||
) -> NSMenuItem {
|
||||
let item = NSMenuItem(title: title, action: action, keyEquivalent: key)
|
||||
|
||||
item.isHidden = isHidden
|
||||
item.target = target
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
private func showLoggedIn(_ user: String?) {
|
||||
if let user {
|
||||
loginMenuItem.title = "Logged in as \(user)"
|
||||
} else {
|
||||
loginMenuItem.title = "Logged in"
|
||||
}
|
||||
loginMenuItem.target = nil
|
||||
logoutMenuItem.isHidden = false
|
||||
connectionMenuItem.isHidden = false
|
||||
}
|
||||
|
||||
private func showLoggedOut() {
|
||||
loginMenuItem.title = "Login"
|
||||
loginMenuItem.target = self
|
||||
|
||||
logoutMenuItem.isHidden = true
|
||||
connectionMenuItem.isHidden = true
|
||||
}
|
||||
|
||||
@objc private func connectButtonTapped() {
|
||||
if appStore?.tunnel.status == .connected {
|
||||
appStore?.tunnel.stop()
|
||||
} else {
|
||||
Task {
|
||||
if let token = appStore?.auth.token {
|
||||
do {
|
||||
try await appStore?.tunnel.start(token: token)
|
||||
} catch {
|
||||
logger.error("error connecting to tunnel: \(String(describing: error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func loginButtonTapped() {
|
||||
Task {
|
||||
do {
|
||||
try await appStore?.auth.signIn()
|
||||
} catch FirezoneError.missingPortalURL {
|
||||
openSettingsWindow()
|
||||
} catch {
|
||||
logger.error("Error signing in: \(String(describing: error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func logoutButtonTapped() {
|
||||
appStore?.auth.signOut()
|
||||
}
|
||||
|
||||
@objc private func settingsButtonTapped() {
|
||||
openSettingsWindow()
|
||||
}
|
||||
|
||||
private func openSettingsWindow() {
|
||||
NSWorkspace.shared.open(URL(string: "firezone://settings")!)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
//
|
||||
// AppStoreTests.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import FirezoneKit
|
||||
|
||||
final class AppStoreTests: XCTestCase {}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
13
swift/apple/FirezoneNetworkExtension/Info.plist
Normal file
13
swift/apple/FirezoneNetworkExtension/Info.plist
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.networkextension.packet-tunnel</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// PacketTunnelProvider.swift
|
||||
// (c) 2023 Firezone, Inc.
|
||||
// LICENSE: Apache-2.0
|
||||
//
|
||||
|
||||
import connlib
|
||||
import Dependencies
|
||||
import NetworkExtension
|
||||
import os
|
||||
|
||||
enum PacketTunnelProviderError: String, Error {
|
||||
case savedProtocolConfigurationIsInvalid
|
||||
case couldNotSetNetworkSettings
|
||||
}
|
||||
|
||||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
static let logger = Logger(subsystem: "dev.firezone.firezone", category: "packet-tunnel")
|
||||
|
||||
private lazy var adapter = Adapter(with: self)
|
||||
|
||||
override func startTunnel(
|
||||
options _: [String: NSObject]? = nil,
|
||||
completionHandler: @escaping (Error?) -> Void
|
||||
) {
|
||||
guard let tunnelProviderProtocol = self.protocolConfiguration as? NETunnelProviderProtocol else {
|
||||
completionHandler(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
|
||||
return
|
||||
}
|
||||
|
||||
let providerConfiguration = tunnelProviderProtocol.providerConfiguration
|
||||
guard let portalURL = providerConfiguration?["portalURL"] as? String else {
|
||||
completionHandler(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
|
||||
return
|
||||
}
|
||||
guard let token = providerConfiguration?["token"] as? String else {
|
||||
completionHandler(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
|
||||
return
|
||||
}
|
||||
|
||||
Self.logger.log("portalURL = \(portalURL, privacy: .public)")
|
||||
Self.logger.log("token = \(token, privacy: .public)")
|
||||
|
||||
do {
|
||||
// Once connlib is updated to take in portalURL and token, this call
|
||||
// should become adapter.start(portalURL: portalURL, token: token)
|
||||
try adapter.start { error in
|
||||
if let error {
|
||||
Self.logger.error("Error in adapter.start: \(error)")
|
||||
}
|
||||
completionHandler(error)
|
||||
}
|
||||
} catch {
|
||||
completionHandler(error)
|
||||
}
|
||||
}
|
||||
|
||||
override func stopTunnel(with _: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||
adapter.stop { error in
|
||||
if let error {
|
||||
Self.logger.error("Error in adapter.stop: \(error)")
|
||||
}
|
||||
completionHandler()
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
// HACK: This is a filthy hack to work around Apple bug 32073323 (dup'd by us as 47526107).
|
||||
// Remove it when they finally fix this upstream and the fix has been rolled out to
|
||||
// sufficient quantities of users.
|
||||
exit(0)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
201
swift/apple/LICENSE
Normal file
201
swift/apple/LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2023 Firezone, Inc.
|
||||
|
||||
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.
|
||||
17
swift/apple/Makefile
Normal file
17
swift/apple/Makefile
Normal file
@@ -0,0 +1,17 @@
|
||||
# Creates a macOS debug build
|
||||
|
||||
PLATFORM=macOS
|
||||
ARCH=$(shell uname -m)
|
||||
|
||||
build-macos:
|
||||
echo "Building debug build for ${PLATFORM}, ${ARCH}"
|
||||
cd ../../rust/connlib/clients/apple && rm -rf ./Connlib.xcframework && ./build-rust.sh && ./build-xcframework-dev.sh
|
||||
@xcodebuild build -scheme Firezone -sdk macosx -destination 'platform=${PLATFORM},arch=${ARCH}'
|
||||
|
||||
clean:
|
||||
@xcodebuild clean -scheme Firezone -sdk macosx -destination 'platform=${PLATFORM},arch=${ARCH}'
|
||||
cd ../../rust/connlib/clients/apple && rm -rf ./Connlib.xcframework
|
||||
|
||||
.PHONY: format
|
||||
format:
|
||||
@swiftformat .
|
||||
1
swift/apple/PortalMock/.tool-versions
Normal file
1
swift/apple/PortalMock/.tool-versions
Normal file
@@ -0,0 +1 @@
|
||||
ruby 3.1.0
|
||||
4
swift/apple/PortalMock/Gemfile
Normal file
4
swift/apple/PortalMock/Gemfile
Normal file
@@ -0,0 +1,4 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem 'sinatra'
|
||||
gem 'thin'
|
||||
32
swift/apple/PortalMock/Gemfile.lock
Normal file
32
swift/apple/PortalMock/Gemfile.lock
Normal file
@@ -0,0 +1,32 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
daemons (1.4.1)
|
||||
eventmachine (1.2.7)
|
||||
mustermann (3.0.0)
|
||||
ruby2_keywords (~> 0.0.1)
|
||||
rack (2.2.4)
|
||||
rack-protection (3.0.4)
|
||||
rack
|
||||
ruby2_keywords (0.0.5)
|
||||
sinatra (3.0.4)
|
||||
mustermann (~> 3.0)
|
||||
rack (~> 2.2, >= 2.2.4)
|
||||
rack-protection (= 3.0.4)
|
||||
tilt (~> 2.0)
|
||||
thin (1.8.1)
|
||||
daemons (~> 1.0, >= 1.0.9)
|
||||
eventmachine (~> 1.0, >= 1.0.4)
|
||||
rack (>= 1, < 3)
|
||||
tilt (2.0.11)
|
||||
|
||||
PLATFORMS
|
||||
arm64-darwin-21
|
||||
arm64-darwin-22
|
||||
|
||||
DEPENDENCIES
|
||||
sinatra
|
||||
thin
|
||||
|
||||
BUNDLED WITH
|
||||
2.3.3
|
||||
13
swift/apple/PortalMock/Info.plist
Normal file
13
swift/apple/PortalMock/Info.plist
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.networkextension.packet-tunnel</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
1
swift/apple/PortalMock/data/client_auth_token
Normal file
1
swift/apple/PortalMock/data/client_auth_token
Normal file
@@ -0,0 +1 @@
|
||||
eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjFMN3k3RUM1T3VSZUNNNnIzX2l0MXNJbjNqeTdiZ2JPSVB3Z0xoejV0SGsifQ.eyJpc3MiOiJodHRwczovL2ZpcmV6b25lLmxvY2FsIiwic3ViIjoidGVzdEBmaXJlem9uZS5kZXYiLCJjbGllbnRfaWQiOiJmaXJlem9uZSIsImV4cCI6MTY3MjgzNzU0NCwiaWF0IjoxNjY4MTMzOTQ0fQ.NvvGWvrMvshKp5MYycDWXa8gQ41Ptrr_nIKzfPWzci8fxwmQYJ5hL1vQpdmECtR5NeGv7qTavi6yq19Kqmwrn27numDXaET2b2xypGbFOm1TJmcbZ4Rxy_-FfAeer-7YNhW_p83a0N7UoPORpxVs8hp76sKe_klfmoM830frrLzeqz0VYxBZXhPiTAlqiG39cY74yk-drxLY4xeRBAXh_TdewrkRkPpTpsrXFz60fF5P8AaRnUKlDSRq89ZIC-zo2ysJsXIZLrJpfcNgkscohZZfXfCLIFaiGvZseW0XHWfq-V5HOXVf09-57GHdmCr-AAJ7sqpnPrSBvg7EDBvylg
|
||||
18
swift/apple/PortalMock/server.rb
Executable file
18
swift/apple/PortalMock/server.rb
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require 'sinatra'
|
||||
require 'erb'
|
||||
|
||||
set :bind, '0.0.0.0'
|
||||
set :port, 4568
|
||||
|
||||
get '/auth' do
|
||||
dest = params['dest']
|
||||
ERB.new("<h1>Auth page</h1><a href=\"/redirect?dest=#{dest}\">Proceed</a>").result(binding)
|
||||
end
|
||||
|
||||
get '/redirect' do
|
||||
dest = params['dest']
|
||||
client_auth_token = File.read('./data/client_auth_token').strip
|
||||
redirect "#{dest}?client_auth_token=#{client_auth_token}"
|
||||
end
|
||||
@@ -8,7 +8,14 @@ Clone this repo:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/firezone/firezone
|
||||
cd swift
|
||||
```
|
||||
|
||||
Build Connlib:
|
||||
```bash
|
||||
cd rust/connlib/clients/apple
|
||||
PLATFORM_NAME=macosx ./build-rust.sh # For macOS
|
||||
PLATFORM_NAME=iphoneos ./build-rust.sh # For iOS
|
||||
./build-xcframework-dev.sh
|
||||
```
|
||||
|
||||
Rename and populate developer team ID file:
|
||||
@@ -23,3 +30,5 @@ Open project in Xcode:
|
||||
```bash
|
||||
open Firezone.xcodeproj
|
||||
```
|
||||
|
||||
Build the Firezone target
|
||||
Reference in New Issue
Block a user