mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 14:05:22 +00:00
Make panic wipe deterministic and device-bound (#1431)
* Make panic wipe deterministic and device-bound * Scope install markers to iOS * Harden panic recovery and service shutdown * Invalidate queued BLE ingress during panic * Harden panic keychain and media cleanup --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: jack <jack@deck.local>
This commit is contained in:
@@ -61,6 +61,7 @@ private final class GatedVoiceCaptureSession: VoiceCaptureSession {
|
||||
private let startError: Error?
|
||||
private(set) var finishStarted = false
|
||||
private(set) var cancelCount = 0
|
||||
private(set) var panicCancelCount = 0
|
||||
private var finishContinuation: CheckedContinuation<URL?, Never>?
|
||||
|
||||
init(startError: Error? = nil) {
|
||||
@@ -83,6 +84,10 @@ private final class GatedVoiceCaptureSession: VoiceCaptureSession {
|
||||
cancelCount += 1
|
||||
}
|
||||
|
||||
func panicCancelSynchronously() {
|
||||
panicCancelCount += 1
|
||||
}
|
||||
|
||||
func resolveFinish(with url: URL?) {
|
||||
let continuation = finishContinuation
|
||||
finishContinuation = nil
|
||||
@@ -204,4 +209,57 @@ struct VoiceCaptureSessionTests {
|
||||
}
|
||||
#expect(viewModel.state == .idle)
|
||||
}
|
||||
|
||||
@Test func panicSynchronouslyCancelsActiveCaptureAndResetsUI() async {
|
||||
let session = GatedVoiceCaptureSession()
|
||||
let viewModel = VoiceRecordingViewModel()
|
||||
viewModel.sessionProvider = { session }
|
||||
|
||||
viewModel.start(shouldShow: true)
|
||||
await waitUntil { self.isRecording(viewModel.state) }
|
||||
|
||||
viewModel.panicWipe()
|
||||
|
||||
#expect(session.panicCancelCount == 1)
|
||||
#expect(viewModel.state == .idle)
|
||||
#expect(!viewModel.isLiveStreaming)
|
||||
}
|
||||
|
||||
@Test func panicInvalidatesARecordingAlreadyFinalizing() async throws {
|
||||
let session = GatedVoiceCaptureSession()
|
||||
let viewModel = VoiceRecordingViewModel()
|
||||
viewModel.sessionProvider = { session }
|
||||
let url = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("voice-panic-\(UUID().uuidString).m4a")
|
||||
try Data([0x01]).write(to: url)
|
||||
var delivered = false
|
||||
|
||||
viewModel.start(shouldShow: true)
|
||||
await waitUntil { self.isRecording(viewModel.state) }
|
||||
viewModel.finish { _ in delivered = true }
|
||||
await waitUntil { session.finishStarted }
|
||||
|
||||
viewModel.panicWipe()
|
||||
session.resolveFinish(with: url)
|
||||
await waitUntil {
|
||||
!FileManager.default.fileExists(atPath: url.path)
|
||||
}
|
||||
|
||||
#expect(!delivered)
|
||||
#expect(viewModel.state == .idle)
|
||||
}
|
||||
|
||||
@Test func liveSessionPanicStopsCaptureWithoutSendingControl() {
|
||||
let capture = StubPTTCapture(stopResult: (nil, 0))
|
||||
var sentPackets: [Data] = []
|
||||
let session = PTTLiveVoiceSession(
|
||||
sendPacket: { sentPackets.append($0) },
|
||||
capture: capture
|
||||
)
|
||||
|
||||
session.panicCancelSynchronously()
|
||||
|
||||
#expect(capture.cancelCount == 1)
|
||||
#expect(sentPackets.isEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user