Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 8da7901

Browse files
committedMar 26, 2025·
feat: add enrichment of StartRequest with OS, device ID, version
1 parent f53a99f commit 8da7901

File tree

5 files changed

+602
-0
lines changed

5 files changed

+602
-0
lines changed
 

‎Coder-Desktop/VPN/Manager.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import VPNLib
66
actor Manager {
77
let ptp: PacketTunnelProvider
88
let cfg: ManagerConfig
9+
let telemetryEnricher: TelemetryEnricher
910

1011
let tunnelHandle: TunnelHandle
1112
let speaker: Speaker<Vpn_ManagerMessage, Vpn_TunnelMessage>
@@ -19,6 +20,7 @@ actor Manager {
1920
init(with: PacketTunnelProvider, cfg: ManagerConfig) async throws(ManagerError) {
2021
ptp = with
2122
self.cfg = cfg
23+
telemetryEnricher = TelemetryEnricher()
2224
#if arch(arm64)
2325
let dylibPath = cfg.serverUrl.appending(path: "bin/coder-vpn-darwin-arm64.dylib")
2426
#elseif arch(x86_64)
@@ -176,6 +178,7 @@ actor Manager {
176178
req.value = header.value
177179
}
178180
}
181+
req = telemetryEnricher.enrich(req)
179182
}
180183
})
181184
} catch {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import Foundation
2+
3+
public struct TelemetryEnricher {
4+
private let deviceID: String
5+
private let version: String?
6+
7+
public init() {
8+
version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
9+
10+
let userDefaults = UserDefaults.standard
11+
let key = "deviceID"
12+
13+
if let existingID = userDefaults.string(forKey: key) {
14+
deviceID = existingID
15+
} else {
16+
let newID = UUID().uuidString
17+
userDefaults.set(newID, forKey: key)
18+
deviceID = newID
19+
}
20+
}
21+
22+
public func enrich(_ original: Vpn_StartRequest) -> Vpn_StartRequest {
23+
var req = original
24+
req.deviceOs = "macOS"
25+
req.deviceID = deviceID
26+
if version != nil {
27+
req.coderDesktopVersion = version!
28+
}
29+
return req
30+
}
31+
}

‎Coder-Desktop/VPNLib/vpn.pb.swift

Lines changed: 492 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Coder-Desktop/VPNLib/vpn.proto

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,26 @@ message TunnelMessage {
4444
}
4545
}
4646

47+
// ClientMessage is a message from the client (to the service). Windows only.
48+
message ClientMessage {
49+
RPC rpc = 1;
50+
oneof msg {
51+
StartRequest start = 2;
52+
StopRequest stop = 3;
53+
StatusRequest status = 4;
54+
}
55+
}
56+
57+
// ServiceMessage is a message from the service (to the client). Windows only.
58+
message ServiceMessage {
59+
RPC rpc = 1;
60+
oneof msg {
61+
StartResponse start = 2;
62+
StopResponse stop = 3;
63+
Status status = 4; // either in reply to a StatusRequest or broadcasted
64+
}
65+
}
66+
4767
// Log is a log message generated by the tunnel. The manager should log it to the system log. It is
4868
// one-way tunnel -> manager with no response.
4969
message Log {
@@ -185,6 +205,12 @@ message StartRequest {
185205
string value = 2;
186206
}
187207
repeated Header headers = 4;
208+
// Device ID from Coder Desktop
209+
string device_id = 5;
210+
// Device OS from Coder Desktop
211+
string device_os = 6;
212+
// Coder Desktop version
213+
string coder_desktop_version = 7;
188214
}
189215

190216
message StartResponse {
@@ -202,3 +228,26 @@ message StopResponse {
202228
bool success = 1;
203229
string error_message = 2;
204230
}
231+
232+
// StatusRequest is a request to get the status of the tunnel. The manager
233+
// replies with a Status.
234+
message StatusRequest {}
235+
236+
// Status is sent in response to a StatusRequest or broadcasted to all clients
237+
// when the status changes.
238+
message Status {
239+
enum Lifecycle {
240+
UNKNOWN = 0;
241+
STARTING = 1;
242+
STARTED = 2;
243+
STOPPING = 3;
244+
STOPPED = 4;
245+
}
246+
Lifecycle lifecycle = 1;
247+
string error_message = 2;
248+
249+
// This will be a FULL update with all workspaces and agents, so clients
250+
// should replace their current peer state. Only the Upserted fields will
251+
// be populated.
252+
PeerUpdate peer_update = 3;
253+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import Testing
2+
@testable import VPNLib
3+
4+
@Suite(.timeLimit(.minutes(1)))
5+
struct TelemetryEnricherTests {
6+
7+
@Test func testEnrichStartRequest() throws {
8+
let enricher0 = TelemetryEnricher()
9+
let original = Vpn_StartRequest.with { req in
10+
req.coderURL = "https://example.com"
11+
req.tunnelFileDescriptor = 123
12+
}
13+
var enriched = enricher0.enrich(original)
14+
#expect(enriched.coderURL == "https://example.com")
15+
#expect(enriched.tunnelFileDescriptor == 123)
16+
#expect(enriched.deviceOs == "macOS")
17+
#expect(enriched.coderDesktopVersion.contains(try Regex(#"^\d+\.\d+\.\d+$"#)))
18+
let deviceID = enriched.deviceID
19+
#expect(!deviceID.isEmpty)
20+
21+
// check we get the same deviceID from a new enricher
22+
let enricher1 = TelemetryEnricher()
23+
enriched = enricher1.enrich(original)
24+
#expect(enriched.deviceID == deviceID)
25+
}
26+
27+
}

0 commit comments

Comments
 (0)
Please sign in to comment.