Skip to content

Commit 24b082a

Browse files
saltukalakusclaude
andcommitted
feat: push relay URL gating via pairing response
- PairingService: add pushRelayUrl to LocalPairingResponse and CloudflarePairingResponse; propagate through toConnectionConfig() - ConnectionConfig: add pushRelayUrl field (Codable, Keychain-persisted) - TransportCredentials: add pushRelayUrl for multi-transport reconnects - AgentManager: save/restore pushRelayUrl in TransportCredentials; pass it when rebuilding ConnectionConfig in connectAgent() - ACPClientWrapper: guard registerPushToken() with pushRelayUrl != nil so push registration is skipped when the bridge has no relay configured Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 26e3f69 commit 24b082a

5 files changed

Lines changed: 27 additions & 7 deletions

File tree

Aptove/Models/ConnectionConfig.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ struct ConnectionConfig: Codable {
1212
let cwd: String
1313
/// Stable bridge agent UUID used for multi-transport deduplication (present in static JSON QR codes).
1414
let agentId: String?
15+
/// Push relay URL from the bridge's pairing response.
16+
/// Non-nil only when the bridge has push fully configured.
17+
/// Mobile clients use this as the gate for registering push tokens.
18+
let pushRelayUrl: String?
1519

1620
enum CodingKeys: String, CodingKey {
1721
case url
@@ -23,9 +27,10 @@ struct ConnectionConfig: Codable {
2327
case version
2428
case cwd
2529
case agentId
30+
case pushRelayUrl
2631
}
2732

28-
init(url: String, clientId: String? = nil, clientSecret: String? = nil, authToken: String? = nil, certFingerprint: String? = nil, protocolVersion: String = "acp", version: String = "1.0.0", cwd: String, agentId: String? = nil) {
33+
init(url: String, clientId: String? = nil, clientSecret: String? = nil, authToken: String? = nil, certFingerprint: String? = nil, protocolVersion: String = "acp", version: String = "1.0.0", cwd: String, agentId: String? = nil, pushRelayUrl: String? = nil) {
2934
self.url = url
3035
self.clientId = clientId
3136
self.clientSecret = clientSecret
@@ -35,6 +40,7 @@ struct ConnectionConfig: Codable {
3540
self.version = version
3641
self.cwd = cwd
3742
self.agentId = agentId
43+
self.pushRelayUrl = pushRelayUrl
3844
}
3945

4046
func validate() throws {

Aptove/Models/TransportCredentials.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ struct TransportCredentials: Codable {
77
let certFingerprint: String?
88
let clientId: String?
99
let clientSecret: String?
10+
/// Push relay URL from the bridge's pairing response (same for all endpoints of a bridge).
11+
let pushRelayUrl: String?
1012
}
1113

1214
// MARK: - Keychain Storage

Aptove/Services/ACPClientWrapper.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,10 +458,16 @@ class ACPClientWrapper: ObservableObject {
458458
print("✅ Session conversation cleared, workspace retained")
459459
}
460460

461-
/// Register the APNs push token with the bridge for background notifications
461+
/// Register the APNs push token with the bridge for background notifications.
462+
/// Skipped when the bridge has no push relay configured (pushRelayUrl is nil).
462463
func registerPushToken() async {
464+
guard config.pushRelayUrl != nil else {
465+
print("📲 ACPClientWrapper: Bridge has no push relay configured, skipping registration")
466+
return
467+
}
468+
463469
let pushManager = PushNotificationManager.shared
464-
470+
465471
guard let deviceToken = await pushManager.getDeviceToken() else {
466472
print("📲 ACPClientWrapper: No push token available, skipping registration")
467473
return

Aptove/Services/AgentManager.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,8 @@ class AgentManager: ObservableObject {
184184
authToken: config.authToken,
185185
certFingerprint: config.certFingerprint,
186186
clientId: config.clientId,
187-
clientSecret: config.clientSecret
187+
clientSecret: config.clientSecret,
188+
pushRelayUrl: config.pushRelayUrl
188189
)
189190
try TransportCredentialManager.save(credentials, for: endpoint.endpointId ?? UUID().uuidString)
190191

@@ -381,7 +382,8 @@ class AgentManager: ObservableObject {
381382
clientSecret: credentials?.clientSecret,
382383
authToken: credentials?.authToken,
383384
certFingerprint: credentials?.certFingerprint,
384-
cwd: entity.cwd
385+
cwd: entity.cwd,
386+
pushRelayUrl: credentials?.pushRelayUrl
385387
)
386388

387389
// Swap client for this endpoint's config

Aptove/Services/PairingService.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ struct LocalPairingResponse: Codable {
111111
let certFingerprint: String?
112112
let agentId: String?
113113
let cwd: String
114+
let pushRelayUrl: String?
114115
}
115116

116117
/// Response from /pair/cloudflare endpoint (future)
@@ -123,6 +124,7 @@ struct CloudflarePairingResponse: Codable {
123124
let clientSecret: String
124125
let agentId: String?
125126
let cwd: String
127+
let pushRelayUrl: String?
126128
}
127129

128130
/// Generic pairing response that can hold either type
@@ -150,7 +152,8 @@ enum PairingResponse {
150152
certFingerprint: response.certFingerprint,
151153
protocolVersion: response.protocol,
152154
version: response.version,
153-
cwd: response.cwd
155+
cwd: response.cwd,
156+
pushRelayUrl: response.pushRelayUrl
154157
)
155158
case .cloudflare(let response):
156159
return ConnectionConfig(
@@ -161,7 +164,8 @@ enum PairingResponse {
161164
certFingerprint: nil,
162165
protocolVersion: response.protocol,
163166
version: response.version,
164-
cwd: response.cwd
167+
cwd: response.cwd,
168+
pushRelayUrl: response.pushRelayUrl
165169
)
166170
}
167171
}

0 commit comments

Comments
 (0)