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

Commit3b95b81

Browse files
committed
feat: install and activate the tunnel provider as network extension
1 parente9f5c6f commit3b95b81

File tree

15 files changed

+386
-28
lines changed

15 files changed

+386
-28
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,7 @@
640640
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
641641
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
642642
CODE_SIGN_ENTITLEMENTS = "Coder Desktop/Coder_Desktop.entitlements";
643+
CODE_SIGN_IDENTITY = "Apple Development";
643644
CODE_SIGN_STYLE = Automatic;
644645
COMBINE_HIDPI_IMAGES = YES;
645646
CURRENT_PROJECT_VERSION = 1;
@@ -659,6 +660,7 @@
659660
MARKETING_VERSION = 1.0;
660661
PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop";
661662
PRODUCT_NAME = "$(TARGET_NAME)";
663+
PROVISIONING_PROFILE_SPECIFIER = "";
662664
SWIFT_EMIT_LOC_STRINGS = YES;
663665
SWIFT_VERSION = 6.0;
664666
};
@@ -670,6 +672,7 @@
670672
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
671673
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
672674
CODE_SIGN_ENTITLEMENTS = "Coder Desktop/Coder_Desktop.entitlements";
675+
CODE_SIGN_IDENTITY = "Apple Development";
673676
CODE_SIGN_STYLE = Automatic;
674677
COMBINE_HIDPI_IMAGES = YES;
675678
CURRENT_PROJECT_VERSION = 1;
@@ -689,6 +692,7 @@
689692
MARKETING_VERSION = 1.0;
690693
PRODUCT_BUNDLE_IDENTIFIER = "com.coder.Coder-Desktop";
691694
PRODUCT_NAME = "$(TARGET_NAME)";
695+
PROVISIONING_PROFILE_SPECIFIER = "";
692696
SWIFT_EMIT_LOC_STRINGS = YES;
693697
SWIFT_VERSION = 6.0;
694698
};
@@ -772,6 +776,7 @@
772776
isa = XCBuildConfiguration;
773777
buildSettings = {
774778
CODE_SIGN_ENTITLEMENTS = VPN/VPN.entitlements;
779+
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
775780
CODE_SIGN_STYLE = Automatic;
776781
CURRENT_PROJECT_VERSION = 1;
777782
DEAD_CODE_STRIPPING = YES;
@@ -799,6 +804,7 @@
799804
isa = XCBuildConfiguration;
800805
buildSettings = {
801806
CODE_SIGN_ENTITLEMENTS = VPN/VPN.entitlements;
807+
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
802808
CODE_SIGN_STYLE = Automatic;
803809
CURRENT_PROJECT_VERSION = 1;
804810
DEAD_CODE_STRIPPING = YES;

‎Coder Desktop/Coder Desktop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎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: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import os
2+
import NetworkExtension
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+
case.failed(let 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+
28+
func configureNetworkExtension(proto:NETunnelProviderProtocol)async{
29+
// removing the old tunnels, rather than reconfiguring ensures that configuration changes
30+
// are picked up.
31+
do{
32+
tryawaitremoveNetworkExtension()
33+
}catch{
34+
logger.error("remove tunnel failed:\(error)")
35+
neState=.failed(error.localizedDescription)
36+
return
37+
}
38+
logger.debug("inserting new tunnel")
39+
40+
lettm=NETunnelProviderManager()
41+
tm.localizedDescription="CoderVPN"
42+
tm.protocolConfiguration= proto
43+
44+
logger.debug("saving new tunnel")
45+
do{
46+
tryawait tm.saveToPreferences()
47+
}catch{
48+
logger.error("save tunnel failed:\(error)")
49+
neState=.failed(error.localizedDescription)
50+
}
51+
return
52+
}
53+
54+
internalfunc removeNetworkExtension()asyncthrows(VPNServiceError){
55+
do{
56+
lettunnels=tryawaitNETunnelProviderManager.loadAllFromPreferences()
57+
fortunnelin tunnels{
58+
tryawait tunnel.removeFromPreferences()
59+
}
60+
}catch{
61+
throwVPNServiceError.internalError("couldn't remove tunnels:\(error)")
62+
}
63+
}
64+
65+
internalfunc enableNetworkExtension()async{
66+
do{
67+
lettm=tryawaitgetTunnelManager()
68+
if !tm.isEnabled{
69+
tm.isEnabled=true
70+
tryawait tm.saveToPreferences()
71+
logger.debug("saved tunnel with enabled=true")
72+
}
73+
try tm.connection.startVPNTunnel()
74+
}catch{
75+
logger.error("enable network extension:\(error)")
76+
neState=.failed(error.localizedDescription)
77+
return
78+
}
79+
logger.debug("enabled and started tunnel")
80+
neState=.enabled
81+
}
82+
83+
internalfunc disableNetworkExtension()async{
84+
do{
85+
lettm=tryawaitgetTunnelManager()
86+
tm.connection.stopVPNTunnel()
87+
tm.isEnabled=false
88+
89+
tryawait tm.saveToPreferences()
90+
}catch{
91+
logger.error("disable network extension:\(error)")
92+
neState=.failed(error.localizedDescription)
93+
return
94+
}
95+
logger.debug("saved tunnel with enabled=false")
96+
neState=.disbled
97+
}
98+
99+
privatefunc getTunnelManager()asyncthrows(VPNServiceError)->NETunnelProviderManager{
100+
vartunnels:[NETunnelProviderManager]=[]
101+
do{
102+
tunnels=tryawaitNETunnelProviderManager.loadAllFromPreferences()
103+
}catch{
104+
throwVPNServiceError.internalError("couldn't load tunnels:\(error)")
105+
}
106+
if tunnels.isEmpty{
107+
throwVPNServiceError.internalError("no tunnels found")
108+
}
109+
return tunnels.first!
110+
}
111+
}
112+
113+
// we're going to mark NETunnelProviderManager as Sendable since there are official APIs that return
114+
// it async.
115+
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,4 +1,5 @@
11
import SwiftUI
2+
import NetworkExtension
23

34
classPreviewSession:Session{
45
@PublishedvarhasSession:Bool
@@ -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,4 +1,5 @@
11
import SwiftUI
2+
import NetworkExtension
23

34
@MainActor
45
finalclassPreviewVPN:Coder_Desktop.VPNService{
@@ -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(self.tunnelProviderProtocol())}
4966
}
5067

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

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

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp