Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit9e0b05b

Browse files
spikecurtisethanndickson
authored andcommitted
feat: install and activate the tunnel provider as network extension
1 parent46c2c09 commit9e0b05b

File tree

14 files changed

+383
-28
lines changed

14 files changed

+383
-28
lines changed

‎Coder Desktop/Coder Desktop.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,7 @@
769769
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
770770
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
771771
CODE_SIGN_ENTITLEMENTS = "Coder Desktop/Coder_Desktop.entitlements";
772+
CODE_SIGN_IDENTITY = "Apple Development";
772773
CODE_SIGN_STYLE = Automatic;
773774
COMBINE_HIDPI_IMAGES = YES;
774775
CURRENT_PROJECT_VERSION = 1;
@@ -788,6 +789,7 @@
788789
MARKETING_VERSION = 1.0;
789790
PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop";
790791
PRODUCT_NAME = "$(TARGET_NAME)";
792+
PROVISIONING_PROFILE_SPECIFIER = "";
791793
SWIFT_EMIT_LOC_STRINGS = YES;
792794
SWIFT_VERSION = 6.0;
793795
};
@@ -799,6 +801,7 @@
799801
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
800802
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
801803
CODE_SIGN_ENTITLEMENTS = "Coder Desktop/Coder_Desktop.entitlements";
804+
CODE_SIGN_IDENTITY = "Apple Development";
802805
CODE_SIGN_STYLE = Automatic;
803806
COMBINE_HIDPI_IMAGES = YES;
804807
CURRENT_PROJECT_VERSION = 1;
@@ -818,6 +821,7 @@
818821
MARKETING_VERSION = 1.0;
819822
PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop";
820823
PRODUCT_NAME = "$(TARGET_NAME)";
824+
PROVISIONING_PROFILE_SPECIFIER = "";
821825
SWIFT_EMIT_LOC_STRINGS = YES;
822826
SWIFT_VERSION = 6.0;
823827
};
@@ -901,6 +905,7 @@
901905
isa = XCBuildConfiguration;
902906
buildSettings = {
903907
CODE_SIGN_ENTITLEMENTS = VPN/VPN.entitlements;
908+
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
904909
CODE_SIGN_STYLE = Automatic;
905910
CURRENT_PROJECT_VERSION = 1;
906911
DEAD_CODE_STRIPPING = YES;
@@ -932,6 +937,7 @@
932937
isa = XCBuildConfiguration;
933938
buildSettings = {
934939
CODE_SIGN_ENTITLEMENTS = VPN/VPN.entitlements;
940+
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
935941
CODE_SIGN_STYLE = Automatic;
936942
CURRENT_PROJECT_VERSION = 1;
937943
DEAD_CODE_STRIPPING = YES;

‎Coder Desktop/Coder Desktop/Coder_Desktop.entitlements

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
<array>
77
<string>packet-tunnel-provider</string>
88
</array>
9+
<key>com.apple.developer.system-extension.install</key>
10+
<true/>
911
<key>com.apple.security.app-sandbox</key>
1012
<true/>
1113
<key>com.apple.security.files.user-selected.read-only</key>

‎Coder Desktop/Coder Desktop/Coder_DesktopApp.swift

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ struct DesktopApp: App {
1111
EmptyView()
1212
}
1313
Window("Sign In", id:Windows.login.rawValue){
14-
LoginForm<PreviewClient,PreviewSession>()
14+
LoginForm<CoderClient,SecureSession>()
1515
}.environmentObject(appDelegate.session)
1616
.windowResizability(.contentSize)
1717
}
@@ -20,18 +20,17 @@ struct DesktopApp: App {
2020
@MainActor
2121
classAppDelegate:NSObject,NSApplicationDelegate{
2222
privatevarmenuBarExtra:FluidMenuBarExtra?
23-
letvpn:PreviewVPN
24-
letsession:PreviewSession
23+
letvpn:CoderVPNService
24+
letsession:SecureSession
2525

2626
overrideinit(){
27-
// TODO: Replace with real implementations
28-
vpn=PreviewVPN()
29-
session=PreviewSession()
27+
vpn=CoderVPNService()
28+
session=SecureSession(onChange: vpn.configureTunnelProviderProtocol)
3029
}
3130

3231
func applicationDidFinishLaunching(_:Notification){
3332
menuBarExtra=FluidMenuBarExtra(title:"Coder Desktop", image:"MenuBarIcon"){
34-
VPNMenu<PreviewVPN,PreviewSession>().frame(width:256)
33+
VPNMenu<CoderVPNService,SecureSession>().frame(width:256)
3534
.environmentObject(self.vpn)
3635
.environmentObject(self.session)
3736
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import NetworkExtension
2+
import os
3+
4+
enumNetworkExtensionState:Equatable{
5+
case unconfigured
6+
case disbled
7+
case enabled
8+
case failed(String)
9+
10+
vardescription:String{
11+
switchself{
12+
case.unconfigured:
13+
return"Not logged in to Coder"
14+
case.enabled:
15+
return"NetworkExtension tunnel enabled"
16+
case.disbled:
17+
return"NetworkExtension tunnel disabled"
18+
caselet.failed(error):
19+
return"NetworkExtension config failed:\(error)"
20+
}
21+
}
22+
}
23+
24+
/// An actor that handles configuring, enabling, and disabling the VPN tunnel via the
25+
/// NetworkExtension APIs.
26+
extensionCoderVPNService{
27+
func configureNetworkExtension(proto:NETunnelProviderProtocol)async{
28+
// removing the old tunnels, rather than reconfiguring ensures that configuration changes
29+
// are picked up.
30+
do{
31+
tryawaitremoveNetworkExtension()
32+
}catch{
33+
logger.error("remove tunnel failed:\(error)")
34+
neState=.failed(error.localizedDescription)
35+
return
36+
}
37+
logger.debug("inserting new tunnel")
38+
39+
lettm=NETunnelProviderManager()
40+
tm.localizedDescription="CoderVPN"
41+
tm.protocolConfiguration= proto
42+
43+
logger.debug("saving new tunnel")
44+
do{
45+
tryawait tm.saveToPreferences()
46+
}catch{
47+
logger.error("save tunnel failed:\(error)")
48+
neState=.failed(error.localizedDescription)
49+
}
50+
}
51+
52+
func removeNetworkExtension()asyncthrows(VPNServiceError){
53+
do{
54+
lettunnels=tryawaitNETunnelProviderManager.loadAllFromPreferences()
55+
fortunnelin tunnels{
56+
tryawait tunnel.removeFromPreferences()
57+
}
58+
}catch{
59+
throwVPNServiceError.internalError("couldn't remove tunnels:\(error)")
60+
}
61+
}
62+
63+
func enableNetworkExtension()async{
64+
do{
65+
lettm=tryawaitgetTunnelManager()
66+
if !tm.isEnabled{
67+
tm.isEnabled=true
68+
tryawait tm.saveToPreferences()
69+
logger.debug("saved tunnel with enabled=true")
70+
}
71+
try tm.connection.startVPNTunnel()
72+
}catch{
73+
logger.error("enable network extension:\(error)")
74+
neState=.failed(error.localizedDescription)
75+
return
76+
}
77+
logger.debug("enabled and started tunnel")
78+
neState=.enabled
79+
}
80+
81+
func disableNetworkExtension()async{
82+
do{
83+
lettm=tryawaitgetTunnelManager()
84+
tm.connection.stopVPNTunnel()
85+
tm.isEnabled=false
86+
87+
tryawait tm.saveToPreferences()
88+
}catch{
89+
logger.error("disable network extension:\(error)")
90+
neState=.failed(error.localizedDescription)
91+
return
92+
}
93+
logger.debug("saved tunnel with enabled=false")
94+
neState=.disbled
95+
}
96+
97+
privatefunc getTunnelManager()asyncthrows(VPNServiceError)->NETunnelProviderManager{
98+
vartunnels:[NETunnelProviderManager]=[]
99+
do{
100+
tunnels=tryawaitNETunnelProviderManager.loadAllFromPreferences()
101+
}catch{
102+
throwVPNServiceError.internalError("couldn't load tunnels:\(error)")
103+
}
104+
if tunnels.isEmpty{
105+
throwVPNServiceError.internalError("no tunnels found")
106+
}
107+
return tunnels.first!
108+
}
109+
}
110+
111+
// we're going to mark NETunnelProviderManager as Sendable since there are official APIs that return
112+
// it async.
113+
extensionNETunnelProviderManager:@unchecked@retroactiveSendable{}

‎Coder Desktop/Coder Desktop/Preview Content/PreviewSession.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import NetworkExtension
12
import SwiftUI
23

34
classPreviewSession:Session{
@@ -21,4 +22,8 @@ class PreviewSession: Session {
2122
hasSession=false
2223
sessionToken=nil
2324
}
25+
26+
func tunnelProviderProtocol()->NETunnelProviderProtocol?{
27+
returnnil
28+
}
2429
}

‎Coder Desktop/Coder Desktop/Preview Content/PreviewVPN.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import NetworkExtension
12
import SwiftUI
23

34
@MainActor
@@ -28,10 +29,10 @@ final class PreviewVPN: Coder_Desktop.VPNService {
2829
do{
2930
tryawaitTask.sleep(for:.seconds(10))
3031
}catch{
31-
state=.failed(.exampleError)
32+
state=.failed(.longTestError)
3233
return
3334
}
34-
state= shouldFail?.failed(.exampleError):.connected
35+
state= shouldFail?.failed(.longTestError):.connected
3536
}
3637

3738
func stop()async{
@@ -40,9 +41,13 @@ final class PreviewVPN: Coder_Desktop.VPNService {
4041
do{
4142
tryawaitTask.sleep(for:.seconds(10))
4243
}catch{
43-
state=.failed(.exampleError)
44+
state=.failed(.longTestError)
4445
return
4546
}
4647
state=.disabled
4748
}
49+
50+
func configureTunnelProviderProtocol(proto _:NETunnelProviderProtocol?){
51+
state=.connecting
52+
}
4853
}

‎Coder Desktop/Coder Desktop/Session.swift

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Foundation
22
import KeychainAccess
3+
import NetworkExtension
34

45
protocolSession:ObservableObject{
56
varhasSession:Bool{get}
@@ -8,9 +9,12 @@ protocol Session: ObservableObject {
89

910
func store(baseAccessURL:URL, sessionToken:String)
1011
func clear()
12+
func tunnelProviderProtocol()->NETunnelProviderProtocol?
1113
}
1214

13-
classSecureSession:ObservableObject{
15+
classSecureSession:ObservableObject&Session{
16+
letappId=Bundle.main.bundleIdentifier!
17+
1418
// Stored in UserDefaults
1519
@Publishedprivate(set)varhasSession:Bool{
1620
didSet{
@@ -31,9 +35,21 @@ class SecureSession: ObservableObject {
3135
}
3236
}
3337

38+
func tunnelProviderProtocol()->NETunnelProviderProtocol?{
39+
if !hasSession{returnnil}
40+
letproto=NETunnelProviderProtocol()
41+
proto.providerBundleIdentifier="\(appId).VPN"
42+
proto.passwordReference=keychain[attributes:Keys.sessionToken]?.persistentRef
43+
proto.serverAddress= baseAccessURL!.absoluteString
44+
return proto
45+
}
46+
3447
privateletkeychain:Keychain
3548

36-
publicinit(){
49+
letonChange:((NETunnelProviderProtocol?)->Void)?
50+
51+
publicinit(onChange:((NETunnelProviderProtocol?)->Void)?=nil){
52+
self.onChange= onChange
3753
keychain=Keychain(service:Bundle.main.bundleIdentifier!)
3854
_hasSession=Published(initialValue:UserDefaults.standard.bool(forKey:Keys.hasSession))
3955
_baseAccessURL=Published(initialValue:UserDefaults.standard.url(forKey:Keys.baseAccessURL))
@@ -46,11 +62,13 @@ class SecureSession: ObservableObject {
4662
hasSession=true
4763
self.baseAccessURL= baseAccessURL
4864
self.sessionToken= sessionToken
65+
iflet onChange{onChange(tunnelProviderProtocol())}
4966
}
5067

5168
publicfunc clear(){
5269
hasSession=false
5370
sessionToken=nil
71+
iflet onChange{onChange(tunnelProviderProtocol())}
5472
}
5573

5674
privatefunc keychainGet(for key:String)->String?{

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp