Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions Apps/iOS/Services/RelayController+ConfigLibrary.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// RelayController+ConfigLibrary.swift
// CellTunnelPhone
//
// Created by Alexander Goodkind <alex@goodkind.io> on 2026-07-22.
// Copyright © 2026, all rights reserved.
//

import CellTunnelLog
import Foundation

private let configLibraryLogger = CellTunnelLog.logger(category: .relay)

// MARK: - Config operations

extension RelayController {
/// Deletes a stored configuration.
func deleteConfig(id: UUID) {
configLibraryLogger.notice("relay controller delete config requested")
Task { await backend.deleteConfig(id: id) }
}

/// Renames a stored configuration without touching tunnel state.
func renameConfig(id: UUID, name: String) {
configLibraryLogger.notice("relay controller rename config requested")
Task { await backend.renameConfig(id: id, name: name) }
}

/// Creates a stored configuration from raw text without leaving it active, for the
/// new-config flow. The agent activates a config on import, so the previously active
/// config is restored afterward to keep New from stealing the current selection.
func createConfig(name: String, text: String) {
configLibraryLogger.notice("relay controller create config requested")
let previousActiveID = activeConfigID
if previousActiveID != nil {
pinnedActiveConfigID = previousActiveID
}
Task {
await backend.importConfig(name: name, text: text)
if let previousActiveID {
await backend.activateConfig(id: previousActiveID)
}
pinnedActiveConfigID = nil
}
}
}
41 changes: 2 additions & 39 deletions Apps/iOS/Services/RelayController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ struct RelayStatusSample: Sendable {
@MainActor
@Observable
final class RelayController {
private let backend: any RelayControlBackend
let backend: any RelayControlBackend
private let installState: InstallationState
private let deviceProbe: DeviceEgressProbe?
private var pollTask: Task<Void, Never>?
Expand Down Expand Up @@ -183,7 +183,7 @@ final class RelayController {
/// cannot momentarily surface the agent's intermediate "new config is active" state
/// between `importConfig` and the restoring `activateConfig`. Non-nil only while a
/// create that preserves a prior active config is in flight.
private var pinnedActiveConfigID: UUID?
var pinnedActiveConfigID: UUID?
/// Whether the background agent is installed, the gate to the install-agent setup
/// tier. Always true on the iPhone, where there is no separate agent; on the Mac
/// it tracks the install state.
Expand Down Expand Up @@ -613,40 +613,3 @@ extension RelayController {
}
}
}

// MARK: - Config operations

extension RelayController {
/// Deletes a stored configuration.
func deleteConfig(id: UUID) {
logger.notice("relay controller delete config requested")
Task { await backend.deleteConfig(id: id) }
}

/// Renames a stored configuration without touching tunnel state.
func renameConfig(id: UUID, name: String) {
logger.notice("relay controller rename config requested")
Task { await backend.renameConfig(id: id, name: name) }
}

/// Creates a stored configuration from raw text without leaving it active, for the
/// new-config flow. The agent activates a config on import, so the previously active
/// config is restored afterward to keep New from stealing the current selection.
func createConfig(name: String, text: String) {
logger.notice("relay controller create config requested")
let previousActiveID = activeConfigID
// Pin the prior active id only when there is one to preserve, so the poll holds
// the checkmark in place until the restore lands; with no prior active config the
// newly imported one stays active and no restore runs.
if previousActiveID != nil {
pinnedActiveConfigID = previousActiveID
}
Task {
await backend.importConfig(name: name, text: text)
if let previousActiveID {
await backend.activateConfig(id: previousActiveID)
}
pinnedActiveConfigID = nil
}
}
}