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
2 changes: 1 addition & 1 deletion FlipcashAPI/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ enum ContractPackage: String, CaseIterable {
var version: Version {
switch self {
case .ocp: return "0.4.0"
case .flipcash2: return "0.7.0"
case .flipcash2: return "0.8.0"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,13 @@ extension FlipClient {

/// Starts a new group chat and returns its metadata on success. `rules` gates who may read/join
/// and who may send; `nil` leaves the chat unrestricted.
public func startChat(owner: KeyPair, title: String, pictureBlobID: BlobID?, rules: ConversationRules?) async throws -> Conversation {
///
/// `idempotencyKey` must be minted by the caller where the user's intent to create the chat
/// originates (not here) and reused for every retry of that same attempt — see
/// `ChatService.startChat`. A retry with the same key returns the original chat.
public func startChat(owner: KeyPair, title: String, pictureBlobID: BlobID?, rules: ConversationRules?, idempotencyKey: UUID) async throws -> Conversation {
try await withCheckedThrowingContinuation { c in
chatService.startChat(owner: owner, title: title, pictureBlobID: pictureBlobID, rules: rules) { c.resume(with: $0) }
chatService.startChat(owner: owner, title: title, pictureBlobID: pictureBlobID, rules: rules, idempotencyKey: idempotencyKey) { c.resume(with: $0) }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,13 @@ final class ChatService: Sendable {
/// restrictions. On `.titleModerated` the server also reports which category flagged the
/// title; `ErrorStartChat.titleModerated` carries it through so callers can say why, not just
/// that the title was rejected.
func startChat(owner: KeyPair, title: String, pictureBlobID: BlobID?, rules: ConversationRules?, completion: @Sendable @escaping (Result<Conversation, ErrorStartChat>) -> Void) {
///
/// `idempotencyKey` is required by the server: caller and key together identify the chat being
/// created, so a retry with the same key returns the original chat (result `.ok`) rather than
/// creating a duplicate, even if `title`/`pictureBlobID`/`rules` differ on the retry. Mint it once
/// where the user's intent to create the chat originates and reuse it for every retry of that same
/// attempt — never generate a fresh key per call, or retries lose their idempotency.
func startChat(owner: KeyPair, title: String, pictureBlobID: BlobID?, rules: ConversationRules?, idempotencyKey: UUID, completion: @Sendable @escaping (Result<Conversation, ErrorStartChat>) -> Void) {
let request = Flipcash_Chat_V1_StartChatRequest.with {
$0.group = .with {
$0.title = title
Expand All @@ -139,6 +145,7 @@ final class ChatService: Sendable {
$0.rules = rules.proto
}
}
$0.idempotencyKey = .with { $0.value = idempotencyKey.data }
$0.auth = owner.authFor(message: $0)
}

Expand Down
Loading