Skip to content

Commit 8b7ec78

Browse files
committed
feat: add coder connect startup progress messages
1 parent 4814176 commit 8b7ec78

File tree

8 files changed

+47
-3
lines changed

8 files changed

+47
-3
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ final class PreviewVPN: Coder_Desktop.VPNService {
3333
self.shouldFail = shouldFail
3434
}
3535

36+
@Published var progressMessage: String?
37+
3638
var startTask: Task<Void, Never>?
3739
func start() async {
3840
if await startTask?.value != nil {

Coder-Desktop/Coder-Desktop/VPN/VPNService.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import VPNLib
77
protocol VPNService: ObservableObject {
88
var state: VPNServiceState { get }
99
var menuState: VPNMenuState { get }
10+
var progressMessage: String? { get }
1011
func start() async
1112
func stop() async
1213
func configureTunnelProviderProtocol(proto: NETunnelProviderProtocol?)
@@ -72,6 +73,8 @@ final class CoderVPNService: NSObject, VPNService {
7273
return tunnelState
7374
}
7475

76+
@Published var progressMessage: String?
77+
7578
@Published var menuState: VPNMenuState = .init()
7679

7780
// Whether the VPN should start as soon as possible
@@ -155,6 +158,10 @@ final class CoderVPNService: NSObject, VPNService {
155158
}
156159
}
157160

161+
func onProgress(_ msg: String?) {
162+
progressMessage = msg
163+
}
164+
158165
func applyPeerUpdate(with update: Vpn_PeerUpdate) {
159166
// Delete agents
160167
update.deletedAgents.forEach { menuState.deleteAgent(withId: $0.id) }

Coder-Desktop/Coder-Desktop/Views/VPN/VPNState.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ struct VPNState<VPN: VPNService>: View {
66

77
let inspection = Inspection<Self>()
88

9+
var progressMessage: String {
10+
if let msg = vpn.progressMessage {
11+
msg
12+
} else {
13+
vpn.state == .connecting ? "Starting Coder Connect..." : "Stopping Coder Connect..."
14+
}
15+
}
16+
917
var body: some View {
1018
Group {
1119
switch (vpn.state, state.hasSession) {
@@ -24,9 +32,11 @@ struct VPNState<VPN: VPNService>: View {
2432
case (.connecting, _), (.disconnecting, _):
2533
HStack {
2634
Spacer()
27-
ProgressView(
28-
vpn.state == .connecting ? "Starting Coder Connect..." : "Stopping Coder Connect..."
29-
).padding()
35+
ProgressView {
36+
Text(progressMessage)
37+
.multilineTextAlignment(.center)
38+
}
39+
.padding()
3040
Spacer()
3141
}
3242
case let (.failed(vpnErr), _):

Coder-Desktop/Coder-Desktop/XPCInterface.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ import VPNLib
7171
}
7272
}
7373

74+
func onProgress(msg: String?) {
75+
Task { @MainActor in
76+
svc.onProgress(msg)
77+
}
78+
}
79+
7480
// The NE has verified the dylib and knows better than Gatekeeper
7581
func removeQuarantine(path: String, reply: @escaping (Bool) -> Void) {
7682
let reply = CallbackWrapper(reply)

Coder-Desktop/Coder-DesktopTests/Util.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class MockVPNService: VPNService, ObservableObject {
1010
@Published var state: Coder_Desktop.VPNServiceState = .disabled
1111
@Published var baseAccessURL: URL = .init(string: "https://dev.coder.com")!
1212
@Published var menuState: VPNMenuState = .init()
13+
@Published var progressMessage: String?
1314
var onStart: (() async -> Void)?
1415
var onStop: (() async -> Void)?
1516

Coder-Desktop/VPN/Manager.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ actor Manager {
3939
} catch {
4040
throw .download(error)
4141
}
42+
pushProgress(msg: "Fetching server version...")
4243
let client = Client(url: cfg.serverUrl)
4344
let buildInfo: BuildInfoResponse
4445
do {
@@ -49,6 +50,7 @@ actor Manager {
4950
guard let semver = buildInfo.semver else {
5051
throw .serverInfo("invalid version: \(buildInfo.version)")
5152
}
53+
pushProgress(msg: "Validating library...")
5254
do {
5355
try SignatureValidator.validate(path: dest, expectedVersion: semver)
5456
} catch {
@@ -59,11 +61,13 @@ actor Manager {
5961
// so it's safe to execute. However, the SE must be sandboxed, so we defer to the app.
6062
try await removeQuarantine(dest)
6163

64+
pushProgress(msg: "Opening library...")
6265
do {
6366
try tunnelHandle = TunnelHandle(dylibPath: dest)
6467
} catch {
6568
throw .tunnelSetup(error)
6669
}
70+
pushProgress(msg: "Setting up tunnel...")
6771
speaker = await Speaker<Vpn_ManagerMessage, Vpn_TunnelMessage>(
6872
writeFD: tunnelHandle.writeHandle,
6973
readFD: tunnelHandle.readHandle
@@ -158,6 +162,7 @@ actor Manager {
158162
}
159163

160164
func startVPN() async throws(ManagerError) {
165+
pushProgress(msg: nil)
161166
logger.info("sending start rpc")
162167
guard let tunFd = ptp.tunnelFileDescriptor else {
163168
logger.error("no fd")
@@ -234,6 +239,15 @@ actor Manager {
234239
}
235240
}
236241

242+
func pushProgress(msg: String?) {
243+
guard let conn = globalXPCListenerDelegate.conn else {
244+
logger.error("couldn't send progress message to app: no connection")
245+
return
246+
}
247+
logger.info("sending progress message to app: \(msg ?? "nil")")
248+
conn.onProgress(msg: msg)
249+
}
250+
237251
struct ManagerConfig {
238252
let apiToken: String
239253
let serverUrl: URL
@@ -312,6 +326,7 @@ private func removeQuarantine(_ dest: URL) async throws(ManagerError) {
312326
let file = NSURL(fileURLWithPath: dest.path)
313327
try? file.getResourceValue(&flag, forKey: kCFURLQuarantinePropertiesKey as URLResourceKey)
314328
if flag != nil {
329+
pushProgress(msg: "Unquarantining download...")
315330
// Try the privileged helper first (it may not even be registered)
316331
if await globalHelperXPCSpeaker.tryRemoveQuarantine(path: dest.path) {
317332
// Success!

Coder-Desktop/VPN/PacketTunnelProvider.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
9292
logger.info("vpn started")
9393
self.manager = manager
9494
completionHandler(nil)
95+
// Clear progress message
96+
pushProgress(msg: nil)
9597
} catch {
9698
logger.error("error starting manager: \(error.description, privacy: .public)")
9799
completionHandler(

Coder-Desktop/VPNLib/XPC.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ import Foundation
1010
@objc public protocol VPNXPCClientCallbackProtocol {
1111
// data is a serialized `Vpn_PeerUpdate`
1212
func onPeerUpdate(_ data: Data)
13+
func onProgress(msg: String?)
1314
func removeQuarantine(path: String, reply: @escaping (Bool) -> Void)
1415
}

0 commit comments

Comments
 (0)