Handle 404s when reading call links

This commit is contained in:
Max Radermacher 2024-10-09 19:28:31 -05:00 committed by GitHub
parent a45958d1a0
commit 8ebabea916
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 64 additions and 17 deletions

View File

@ -57,13 +57,19 @@ actor CallLinkStateUpdater {
rootKey: CallLinkRootKey,
updateAndFetch: (CallLinkAuthCredential) async throws -> SignalServiceKit.CallLinkState
) async throws -> SignalServiceKit.CallLinkState {
return try await _updateExclusively(rootKey: rootKey, updateAndFetch: updateAndFetch)!
return try await _updateExclusively(rootKey: rootKey, updateAndFetch: updateAndFetch)!.get()
}
private enum UpdateAction {
case update(SignalServiceKit.CallLinkState)
case notFound
case delete
}
private func _updateExclusively(
rootKey: CallLinkRootKey,
updateAndFetch: (CallLinkAuthCredential) async throws -> SignalServiceKit.CallLinkState?
) async throws -> SignalServiceKit.CallLinkState? {
) async throws -> Result<SignalServiceKit.CallLinkState, CallLinkNotFoundError>? {
let roomId = rootKey.deriveRoomId()
await withCheckedContinuation { continuation in
@ -90,13 +96,34 @@ actor CallLinkStateUpdater {
return try callLinkStore.fetch(roomId: roomId, tx: tx)
}
let authCredential = try await authCredentialManager.fetchCallLinkAuthCredential(localIdentifiers: localIdentifiers)
let newState = try await updateAndFetch(authCredential)
let updateResult = await Result { try await updateAndFetch(authCredential) }
let updateAction: UpdateAction
let returnResult: Result<SignalServiceKit.CallLinkState, CallLinkNotFoundError>?
switch updateResult {
case .success(let callLinkState?):
updateAction = .update(callLinkState)
returnResult = .success(callLinkState)
case .success(nil):
updateAction = .delete
returnResult = nil
case .failure(let error as CallLinkNotFoundError):
updateAction = .notFound
returnResult = .failure(error)
case .failure(let error):
throw error
}
try await db.awaitableWrite { tx in
if var newRecord = try self.callLinkStore.fetch(roomId: roomId, tx: tx) {
if !newRecord.isDeleted {
if let newState {
switch updateAction {
case .update(let newState):
newRecord.updateState(newState)
} else {
case .notFound:
break
case .delete:
newRecord.markDeleted(atTimestampMs: Date.ows_millisecondTimestamp())
try self.callRecordDeleteManager.deleteCallRecords(
self.callRecordStore.fetchExisting(conversationId: .callLink(callLinkRowId: newRecord.id), limit: nil, tx: tx),
@ -111,13 +138,27 @@ actor CallLinkStateUpdater {
try self.callLinkStore.update(newRecord, tx: tx)
}
}
return newState
return returnResult
}
func readCallLink(rootKey: CallLinkRootKey) async throws -> SignalServiceKit.CallLinkState {
return try await updateExclusively(rootKey: rootKey, updateAndFetch: { authCredential in
/// Reads a call link from the server.
///
/// There are two layers of errors interesting to callers: the method itself
/// and the `Result` that's returned.
///
/// This is a "state updater" object, so if the "state update" operation is
/// successful, no error is thrown. The "state update" is successful when
/// we're able to call `clearNeedsFetch` on the underlying CallLinkRecord.
/// (For example, no error is thrown when the call link can't be found, but
/// an error *is* thrown when there's no network.)
///
/// Many callers will want access to the `CallLinkState`, and they can use
/// `try readCallLink(...).get()` to gloss over this distinction.
func readCallLink(rootKey: CallLinkRootKey) async throws -> Result<SignalServiceKit.CallLinkState, CallLinkNotFoundError> {
return try await _updateExclusively(rootKey: rootKey, updateAndFetch: { authCredential in
return try await callLinkFetcher.readCallLink(rootKey, authCredential: authCredential)
})
})!
}
func deleteCallLink(rootKey: CallLinkRootKey, adminPasskey: Data) async throws {

View File

@ -569,7 +569,7 @@ final class CallService: CallServiceStateObserver, CallServiceStateDelegate {
case .reuse(let callLinkState):
state = callLinkState
case .fetch:
state = try await callLinkStateUpdater.readCallLink(rootKey: callLink.rootKey)
state = try await callLinkStateUpdater.readCallLink(rootKey: callLink.rootKey).get()
}
let localIdentifiers = DependenciesBridge.shared.tsAccountManager.localIdentifiersWithMaybeSneakyTransaction!
let authCredential = try await authCredentialManager.fetchCallLinkAuthCredential(localIdentifiers: localIdentifiers)

View File

@ -8,6 +8,8 @@ import LibSignalClient
public import SignalRingRTC
public import SignalServiceKit
public struct CallLinkNotFoundError: Error {}
public class CallLinkFetcherImpl {
private let sfuClient: SFUClient
// Even though we never use this, we need to retain it to ensure
@ -27,20 +29,24 @@ public class CallLinkFetcherImpl {
let sfuUrl = DebugFlags.callingUseTestSFU.get() ? TSConstants.sfuTestURL : TSConstants.sfuURL
let secretParams = CallLinkSecretParams.deriveFromRootKey(rootKey.bytes)
let authCredentialPresentation = authCredential.present(callLinkParams: secretParams)
return SignalServiceKit.CallLinkState(try await self.sfuClient.readCallLink(
sfuUrl: sfuUrl,
authCredentialPresentation: authCredentialPresentation.serialize(),
linkRootKey: rootKey
).unwrap())
do {
return try await SignalServiceKit.CallLinkState(self.sfuClient.readCallLink(
sfuUrl: sfuUrl,
authCredentialPresentation: authCredentialPresentation.serialize(),
linkRootKey: rootKey
).unwrap())
} catch where error.rawValue == 404 {
throw CallLinkNotFoundError()
}
}
}
private struct SFUError: Error {
public struct SFUError: Error {
let rawValue: UInt16
}
extension SFUResult {
public func unwrap() throws -> Value {
public func unwrap() throws(SFUError) -> Value {
switch self {
case .success(let value):
return value