diff --git a/Package.swift b/Package.swift index 54834c36..4f5a4620 100644 --- a/Package.swift +++ b/Package.swift @@ -24,8 +24,8 @@ let package = Package( targets: [ .binaryTarget( name: "LibXMTPSwiftFFI", - url: "https://github.com/xmtp/libxmtp/releases/download/swift-bindings-1.5.6.fe6e305/LibXMTPSwiftFFI.zip", - checksum: "866a6091be3c85d69d9c0ea1a43f08d44450afd914ceb4c8179116660c23444e" + url: "https://github.com/xmtp/libxmtp/releases/download/swift-bindings-1.6.0-dev.c046238/LibXMTPSwiftFFI.zip", + checksum: "c011e963334de61a3bf7d81164a127443c68486b3722912203a76ad2d0771d5f" ), .target( name: "XMTPiOS", diff --git a/Sources/XMTPiOS/Client.swift b/Sources/XMTPiOS/Client.swift index 951ceac9..1d37a32b 100644 --- a/Sources/XMTPiOS/Client.swift +++ b/Sources/XMTPiOS/Client.swift @@ -36,14 +36,18 @@ public struct ClientOptions { public var isSecure: Bool = true public var appVersion: String? = nil + + public var gatewayUrl: String? = nil public init( env: XMTPEnvironment = .dev, isSecure: Bool = true, - appVersion: String? = nil + appVersion: String? = nil, + gatewayUrl: String? = nil ) { self.env = env self.isSecure = isSecure self.appVersion = appVersion + self.gatewayUrl = gatewayUrl } } @@ -86,9 +90,30 @@ public struct ClientOptions { } } +/// Cache for API clients to avoid creating duplicate connections. +/// Cache keys are constructed from v3Host, gatewayHost, isSecure, and appVersion +/// to ensure proper isolation between different API configurations. actor ApiClientCache { private var apiClientCache: [String: XmtpApiClient] = [:] private var syncApiClientCache: [String: XmtpApiClient] = [:] + + /// Creates a cache key from API configuration parameters + /// - Parameters: + /// - v3Host: The v3 host URL + /// - gatewayHost: The gateway host URL (optional) + /// - isSecure: Whether the connection uses TLS + /// - appVersion: The app version string (optional) + /// - Returns: A unique cache key string + static func makeCacheKey( + v3Host: String, + gatewayHost: String?, + isSecure: Bool, + appVersion: String? + ) -> String { + let gateway = gatewayHost ?? "none" + let version = appVersion ?? "none" + return "\(v3Host)|\(gateway)|\(isSecure)|\(version)" + } func getClient(forKey key: String) -> XmtpApiClient? { return apiClientCache[key] @@ -362,7 +387,12 @@ public final class Client { public static func connectToApiBackend(api: ClientOptions.Api) async throws -> XmtpApiClient { - let cacheKey = api.env.url + let cacheKey = ApiClientCache.makeCacheKey( + v3Host: api.env.url, + gatewayHost: api.gatewayUrl, + isSecure: api.isSecure, + appVersion: api.appVersion + ) // Check for an existing connected client if let cached = await apiCache.getClient(forKey: cacheKey), @@ -373,7 +403,8 @@ public final class Client { // Either not cached or not connected; create new client let newClient = try await connectToBackend( - host: api.env.url, + v3Host: api.env.url, + gatewayHost: api.gatewayUrl, isSecure: api.isSecure, appVersion: api.appVersion ) @@ -385,7 +416,12 @@ public final class Client { async throws -> XmtpApiClient { - let cacheKey = api.env.url + let cacheKey = ApiClientCache.makeCacheKey( + v3Host: api.env.url, + gatewayHost: api.gatewayUrl, + isSecure: api.isSecure, + appVersion: api.appVersion + ) // Check for an existing connected client if let cached = await apiCache.getSyncClient(forKey: cacheKey), @@ -396,7 +432,8 @@ public final class Client { // Either not cached or not connected; create new client let newClient = try await connectToBackend( - host: api.env.url, + v3Host: api.env.url, + gatewayHost: api.gatewayUrl, isSecure: api.isSecure, appVersion: api.appVersion ) diff --git a/Sources/XMTPiOS/Conversation.swift b/Sources/XMTPiOS/Conversation.swift index a96aba1a..39846b68 100644 --- a/Sources/XMTPiOS/Conversation.swift +++ b/Sources/XMTPiOS/Conversation.swift @@ -137,17 +137,21 @@ public enum Conversation: Identifiable, Equatable, Hashable { } } - public func prepareMessage(encodedContent: EncodedContent) async throws - -> String - { - switch self { - case let .group(group): - return try await group.prepareMessage( - encodedContent: encodedContent) - case let .dm(dm): - return try await dm.prepareMessage(encodedContent: encodedContent) - } - } + public func prepareMessage( + encodedContent: EncodedContent, visibilityOptions: MessageVisibilityOptions? = nil + ) async throws -> String + { + switch self { + case let .group(group): + return try await group.prepareMessage( + encodedContent: encodedContent, visibilityOptions: visibilityOptions + ) + case let .dm(dm): + return try await dm.prepareMessage( + encodedContent: encodedContent, visibilityOptions: visibilityOptions + ) + } + } public func prepareMessage(content: T, options: SendOptions? = nil) async throws -> String @@ -218,17 +222,20 @@ public enum Conversation: Identifiable, Equatable, Hashable { } } - @discardableResult public func send( - encodedContent: EncodedContent - ) async throws -> String { - switch self { - case let .group(group): - return try await group.send( - encodedContent: encodedContent) - case let .dm(dm): - return try await dm.send(encodedContent: encodedContent) - } - } + @discardableResult public func send( + encodedContent: EncodedContent, visibilityOptions: MessageVisibilityOptions? = nil + ) async throws -> String { + switch self { + case let .group(group): + return try await group.send( + encodedContent: encodedContent, visibilityOptions: visibilityOptions + ) + case let .dm(dm): + return try await dm.send( + encodedContent: encodedContent, visibilityOptions: visibilityOptions + ) + } + } public func send(text: String, options: SendOptions? = nil) async throws -> String diff --git a/Sources/XMTPiOS/Dm.swift b/Sources/XMTPiOS/Dm.swift index b3d6b0b5..18ec3ce6 100644 --- a/Sources/XMTPiOS/Dm.swift +++ b/Sources/XMTPiOS/Dm.swift @@ -147,19 +147,25 @@ public struct Dm: Identifiable, Equatable, Hashable { public func send(content: T, options: SendOptions? = nil) async throws -> String { - let encodeContent = try await encodeContent( - content: content, options: options) - return try await send(encodedContent: encodeContent) + let (encodeContent, visibilityOptions) = try await encodeContent( + content: content, options: options + ) + return try await send(encodedContent: encodeContent, visibilityOptions: visibilityOptions) } - public func send(encodedContent: EncodedContent) async throws -> String { + public func send( + encodedContent: EncodedContent, visibilityOptions: MessageVisibilityOptions? = nil + ) async throws -> String { + let opts = visibilityOptions?.toFfi() ?? FfiSendMessageOpts(shouldPush: true) let messageId = try await ffiConversation.send( - contentBytes: encodedContent.serializedData()) + contentBytes: encodedContent.serializedData(), + opts: opts + ) return messageId.toHex } public func encodeContent(content: T, options: SendOptions?) async throws - -> EncodedContent + -> (EncodedContent, MessageVisibilityOptions) { let codec = Client.codecRegistry.find(for: options?.contentType) @@ -193,24 +199,45 @@ public struct Dm: Identifiable, Equatable, Hashable { encoded = try encoded.compress(compression) } - return encoded + func shouldPush(codec: Codec, content: Any) throws + -> Bool + { + if let content = content as? Codec.T { + return try codec.shouldPush(content: content) + } else { + throw CodecError.invalidContent + } + } + + let visibilityOptions = try MessageVisibilityOptions( + shouldPush: shouldPush(codec: codec, content: content) + ) + + return (encoded, visibilityOptions) } - public func prepareMessage(encodedContent: EncodedContent) async throws + public func prepareMessage( + encodedContent: EncodedContent, visibilityOptions: MessageVisibilityOptions? = nil + ) async throws -> String { + let opts = visibilityOptions?.toFfi() ?? FfiSendMessageOpts(shouldPush: true) let messageId = try ffiConversation.sendOptimistic( - contentBytes: encodedContent.serializedData()) + contentBytes: encodedContent.serializedData(), + opts: opts + ) return messageId.toHex } public func prepareMessage(content: T, options: SendOptions? = nil) async throws -> String { - let encodeContent = try await encodeContent( - content: content, options: options) + let (encodeContent, visibilityOptions) = try await encodeContent( + content: content, options: options + ) return try ffiConversation.sendOptimistic( - contentBytes: try encodeContent.serializedData() + contentBytes: encodeContent.serializedData(), + opts: visibilityOptions.toFfi() ).toHex } diff --git a/Sources/XMTPiOS/Group.swift b/Sources/XMTPiOS/Group.swift index 5885da79..6d14b55d 100644 --- a/Sources/XMTPiOS/Group.swift +++ b/Sources/XMTPiOS/Group.swift @@ -322,15 +322,21 @@ public struct Group: Identifiable, Equatable, Hashable { public func send(content: T, options: SendOptions? = nil) async throws -> String { - let encodeContent = try await encodeContent( - content: content, options: options) - return try await send(encodedContent: encodeContent) + let (encodeContent, visibilityOptions) = try await encodeContent( + content: content, options: options + ) + return try await send(encodedContent: encodeContent, visibilityOptions: visibilityOptions) } - public func send(encodedContent: EncodedContent) async throws -> String { + public func send( + encodedContent: EncodedContent, visibilityOptions: MessageVisibilityOptions? = nil + ) async throws -> String { do { + let opts = visibilityOptions?.toFfi() ?? FfiSendMessageOpts(shouldPush: true) let messageId = try await ffiGroup.send( - contentBytes: encodedContent.serializedData()) + contentBytes: encodedContent.serializedData(), + opts: opts + ) return messageId.toHex } catch { throw error @@ -338,7 +344,7 @@ public struct Group: Identifiable, Equatable, Hashable { } public func encodeContent(content: T, options: SendOptions?) async throws - -> EncodedContent + -> (EncodedContent, MessageVisibilityOptions) { let codec = Client.codecRegistry.find(for: options?.contentType) @@ -372,24 +378,45 @@ public struct Group: Identifiable, Equatable, Hashable { encoded = try encoded.compress(compression) } - return encoded + func shouldPush(codec: Codec, content: Any) throws + -> Bool + { + if let content = content as? Codec.T { + return try codec.shouldPush(content: content) + } else { + throw CodecError.invalidContent + } + } + + let visibilityOptions = try MessageVisibilityOptions( + shouldPush: shouldPush(codec: codec, content: content) + ) + + return (encoded, visibilityOptions) } - public func prepareMessage(encodedContent: EncodedContent) async throws + public func prepareMessage( + encodedContent: EncodedContent, visibilityOptions: MessageVisibilityOptions? = nil + ) async throws -> String { + let opts = visibilityOptions?.toFfi() ?? FfiSendMessageOpts(shouldPush: true) let messageId = try ffiGroup.sendOptimistic( - contentBytes: encodedContent.serializedData()) + contentBytes: encodedContent.serializedData(), + opts: opts + ) return messageId.toHex } public func prepareMessage(content: T, options: SendOptions? = nil) async throws -> String { - let encodeContent = try await encodeContent( - content: content, options: options) + let (encodeContent, visibilityOptions) = try await encodeContent( + content: content, options: options + ) return try ffiGroup.sendOptimistic( - contentBytes: try encodeContent.serializedData() + contentBytes: encodeContent.serializedData(), + opts: visibilityOptions.toFfi() ).toHex } diff --git a/Sources/XMTPiOS/Libxmtp/xmtpv3.swift b/Sources/XMTPiOS/Libxmtp/xmtpv3.swift index 730dfa49..bd7dcdb5 100644 --- a/Sources/XMTPiOS/Libxmtp/xmtpv3.swift +++ b/Sources/XMTPiOS/Libxmtp/xmtpv3.swift @@ -900,12 +900,12 @@ public protocol FfiConversationProtocol: AnyObject, Sendable { func removeSuperAdmin(inboxId: String) async throws - func send(contentBytes: Data) async throws -> Data + func send(contentBytes: Data, opts: FfiSendMessageOpts) async throws -> Data /** * send a message without immediately publishing to the delivery service. */ - func sendOptimistic(contentBytes: Data) throws -> Data + func sendOptimistic(contentBytes: Data, opts: FfiSendMessageOpts) throws -> Data func sendText(text: String) async throws -> Data @@ -1406,13 +1406,13 @@ open func removeSuperAdmin(inboxId: String)async throws { ) } -open func send(contentBytes: Data)async throws -> Data { +open func send(contentBytes: Data, opts: FfiSendMessageOpts)async throws -> Data { return try await uniffiRustCallAsync( rustFutureFunc: { uniffi_xmtpv3_fn_method_fficonversation_send( self.uniffiClonePointer(), - FfiConverterData.lower(contentBytes) + FfiConverterData.lower(contentBytes),FfiConverterTypeFfiSendMessageOpts_lower(opts) ) }, pollFunc: ffi_xmtpv3_rust_future_poll_rust_buffer, @@ -1426,10 +1426,11 @@ open func send(contentBytes: Data)async throws -> Data { /** * send a message without immediately publishing to the delivery service. */ -open func sendOptimistic(contentBytes: Data)throws -> Data { +open func sendOptimistic(contentBytes: Data, opts: FfiSendMessageOpts)throws -> Data { return try FfiConverterData.lift(try rustCallWithError(FfiConverterTypeGenericError_lift) { uniffi_xmtpv3_fn_method_fficonversation_send_optimistic(self.uniffiClonePointer(), - FfiConverterData.lower(contentBytes),$0 + FfiConverterData.lower(contentBytes), + FfiConverterTypeFfiSendMessageOpts_lower(opts),$0 ) }) } @@ -3701,7 +3702,7 @@ public protocol FfiSignatureRequestProtocol: AnyObject, Sendable { func isReady() async -> Bool /** - * missing signatures that are from [MemberKind::Address] + * missing signatures that are from `MemberKind::Address` */ func missingAddressSignatures() async throws -> [String] @@ -3830,7 +3831,7 @@ open func isReady()async -> Bool { } /** - * missing signatures that are from [MemberKind::Address] + * missing signatures that are from `MemberKind::Address` */ open func missingAddressSignatures()async throws -> [String] { return @@ -4288,6 +4289,8 @@ public protocol FfiXmtpClientProtocol: AnyObject, Sendable { func dbReconnect() async throws + func deleteMessage(messageId: Data) throws -> UInt32 + func dmConversation(targetInboxId: String) throws -> FfiConversation func findInboxId(identifier: FfiIdentifier) async throws -> String? @@ -4620,6 +4623,14 @@ open func dbReconnect()async throws { ) } +open func deleteMessage(messageId: Data)throws -> UInt32 { + return try FfiConverterUInt32.lift(try rustCallWithError(FfiConverterTypeGenericError_lift) { + uniffi_xmtpv3_fn_method_ffixmtpclient_delete_message(self.uniffiClonePointer(), + FfiConverterData.lower(messageId),$0 + ) +}) +} + open func dmConversation(targetInboxId: String)throws -> FfiConversation { return try FfiConverterTypeFfiConversation_lift(try rustCallWithError(FfiConverterTypeGenericError_lift) { uniffi_xmtpv3_fn_method_ffixmtpclient_dm_conversation(self.uniffiClonePointer(), @@ -5026,9 +5037,15 @@ public func FfiConverterTypeFfiXmtpClient_lower(_ value: FfiXmtpClient) -> Unsaf +/** + * the opaque Xmtp Api Client for iOS/Android bindings + */ public protocol XmtpApiClientProtocol: AnyObject, Sendable { } +/** + * the opaque Xmtp Api Client for iOS/Android bindings + */ open class XmtpApiClient: XmtpApiClientProtocol, @unchecked Sendable { fileprivate let pointer: UnsafeMutableRawPointer! @@ -5676,11 +5693,11 @@ public struct FfiConversationDebugInfo { public var isCommitLogForked: Bool? public var localCommitLog: String public var remoteCommitLog: String - public var cursor: Int64 + public var cursor: [FfiCursor] // Default memberwise initializers are never public by default, so we // declare one manually. - public init(epoch: UInt64, maybeForked: Bool, forkDetails: String, isCommitLogForked: Bool?, localCommitLog: String, remoteCommitLog: String, cursor: Int64) { + public init(epoch: UInt64, maybeForked: Bool, forkDetails: String, isCommitLogForked: Bool?, localCommitLog: String, remoteCommitLog: String, cursor: [FfiCursor]) { self.epoch = epoch self.maybeForked = maybeForked self.forkDetails = forkDetails @@ -5748,7 +5765,7 @@ public struct FfiConverterTypeFfiConversationDebugInfo: FfiConverterRustBuffer { isCommitLogForked: FfiConverterOptionBool.read(from: &buf), localCommitLog: FfiConverterString.read(from: &buf), remoteCommitLog: FfiConverterString.read(from: &buf), - cursor: FfiConverterInt64.read(from: &buf) + cursor: FfiConverterSequenceTypeFfiCursor.read(from: &buf) ) } @@ -5759,7 +5776,7 @@ public struct FfiConverterTypeFfiConversationDebugInfo: FfiConverterRustBuffer { FfiConverterOptionBool.write(value.isCommitLogForked, into: &buf) FfiConverterString.write(value.localCommitLog, into: &buf) FfiConverterString.write(value.remoteCommitLog, into: &buf) - FfiConverterInt64.write(value.cursor, into: &buf) + FfiConverterSequenceTypeFfiCursor.write(value.cursor, into: &buf) } } @@ -6037,6 +6054,76 @@ public func FfiConverterTypeFfiCreateGroupOptions_lower(_ value: FfiCreateGroupO } +public struct FfiCursor { + public var originatorId: UInt32 + public var sequenceId: UInt64 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(originatorId: UInt32, sequenceId: UInt64) { + self.originatorId = originatorId + self.sequenceId = sequenceId + } +} + +#if compiler(>=6) +extension FfiCursor: Sendable {} +#endif + + +extension FfiCursor: Equatable, Hashable { + public static func ==(lhs: FfiCursor, rhs: FfiCursor) -> Bool { + if lhs.originatorId != rhs.originatorId { + return false + } + if lhs.sequenceId != rhs.sequenceId { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(originatorId) + hasher.combine(sequenceId) + } +} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeFfiCursor: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> FfiCursor { + return + try FfiCursor( + originatorId: FfiConverterUInt32.read(from: &buf), + sequenceId: FfiConverterUInt64.read(from: &buf) + ) + } + + public static func write(_ value: FfiCursor, into buf: inout [UInt8]) { + FfiConverterUInt32.write(value.originatorId, into: &buf) + FfiConverterUInt64.write(value.sequenceId, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeFfiCursor_lift(_ buf: RustBuffer) throws -> FfiCursor { + return try FfiConverterTypeFfiCursor.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeFfiCursor_lower(_ value: FfiCursor) -> RustBuffer { + return FfiConverterTypeFfiCursor.lower(value) +} + + public struct FfiDecodedMessageMetadata { public var id: Data public var sentAtNs: Int64 @@ -7203,11 +7290,12 @@ public struct FfiMessage { public var content: Data public var kind: FfiConversationMessageKind public var deliveryStatus: FfiDeliveryStatus - public var sequenceId: UInt64? + public var sequenceId: UInt64 + public var originatorId: UInt32 // Default memberwise initializers are never public by default, so we // declare one manually. - public init(id: Data, sentAtNs: Int64, conversationId: Data, senderInboxId: String, content: Data, kind: FfiConversationMessageKind, deliveryStatus: FfiDeliveryStatus, sequenceId: UInt64?) { + public init(id: Data, sentAtNs: Int64, conversationId: Data, senderInboxId: String, content: Data, kind: FfiConversationMessageKind, deliveryStatus: FfiDeliveryStatus, sequenceId: UInt64, originatorId: UInt32) { self.id = id self.sentAtNs = sentAtNs self.conversationId = conversationId @@ -7216,6 +7304,7 @@ public struct FfiMessage { self.kind = kind self.deliveryStatus = deliveryStatus self.sequenceId = sequenceId + self.originatorId = originatorId } } @@ -7250,6 +7339,9 @@ extension FfiMessage: Equatable, Hashable { if lhs.sequenceId != rhs.sequenceId { return false } + if lhs.originatorId != rhs.originatorId { + return false + } return true } @@ -7262,6 +7354,7 @@ extension FfiMessage: Equatable, Hashable { hasher.combine(kind) hasher.combine(deliveryStatus) hasher.combine(sequenceId) + hasher.combine(originatorId) } } @@ -7281,7 +7374,8 @@ public struct FfiConverterTypeFfiMessage: FfiConverterRustBuffer { content: FfiConverterData.read(from: &buf), kind: FfiConverterTypeFfiConversationMessageKind.read(from: &buf), deliveryStatus: FfiConverterTypeFfiDeliveryStatus.read(from: &buf), - sequenceId: FfiConverterOptionUInt64.read(from: &buf) + sequenceId: FfiConverterUInt64.read(from: &buf), + originatorId: FfiConverterUInt32.read(from: &buf) ) } @@ -7293,7 +7387,8 @@ public struct FfiConverterTypeFfiMessage: FfiConverterRustBuffer { FfiConverterData.write(value.content, into: &buf) FfiConverterTypeFfiConversationMessageKind.write(value.kind, into: &buf) FfiConverterTypeFfiDeliveryStatus.write(value.deliveryStatus, into: &buf) - FfiConverterOptionUInt64.write(value.sequenceId, into: &buf) + FfiConverterUInt64.write(value.sequenceId, into: &buf) + FfiConverterUInt32.write(value.originatorId, into: &buf) } } @@ -8266,6 +8361,68 @@ public func FfiConverterTypeFfiReply_lower(_ value: FfiReply) -> RustBuffer { } +public struct FfiSendMessageOpts { + public var shouldPush: Bool + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(shouldPush: Bool) { + self.shouldPush = shouldPush + } +} + +#if compiler(>=6) +extension FfiSendMessageOpts: Sendable {} +#endif + + +extension FfiSendMessageOpts: Equatable, Hashable { + public static func ==(lhs: FfiSendMessageOpts, rhs: FfiSendMessageOpts) -> Bool { + if lhs.shouldPush != rhs.shouldPush { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(shouldPush) + } +} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeFfiSendMessageOpts: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> FfiSendMessageOpts { + return + try FfiSendMessageOpts( + shouldPush: FfiConverterBool.read(from: &buf) + ) + } + + public static func write(_ value: FfiSendMessageOpts, into buf: inout [UInt8]) { + FfiConverterBool.write(value.shouldPush, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeFfiSendMessageOpts_lift(_ buf: RustBuffer) throws -> FfiSendMessageOpts { + return try FfiConverterTypeFfiSendMessageOpts.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeFfiSendMessageOpts_lower(_ value: FfiSendMessageOpts) -> RustBuffer { + return FfiConverterTypeFfiSendMessageOpts.lower(value) +} + + public struct FfiTextContent { public var content: String @@ -11449,6 +11606,8 @@ public enum GenericError: Swift.Error { case Expired(message: String) + case BackendBuilder(message: String) + } @@ -11565,6 +11724,10 @@ public struct FfiConverterTypeGenericError: FfiConverterRustBuffer { message: try FfiConverterString.read(from: &buf) ) + case 26: return .BackendBuilder( + message: try FfiConverterString.read(from: &buf) + ) + default: throw UniffiInternalError.unexpectedEnumCase } @@ -11626,6 +11789,8 @@ public struct FfiConverterTypeGenericError: FfiConverterRustBuffer { writeInt(&buf, Int32(24)) case .Expired(_ /* message is ignored*/): writeInt(&buf, Int32(25)) + case .BackendBuilder(_ /* message is ignored*/): + writeInt(&buf, Int32(26)) } @@ -12657,6 +12822,31 @@ fileprivate struct FfiConverterSequenceTypeFfiConversationMember: FfiConverterRu } } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypeFfiCursor: FfiConverterRustBuffer { + typealias SwiftType = [FfiCursor] + + public static func write(_ value: [FfiCursor], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeFfiCursor.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [FfiCursor] { + let len: Int32 = try readInt(&buf) + var seq = [FfiCursor]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeFfiCursor.read(from: &buf)) + } + return seq + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -13225,11 +13415,17 @@ public func applySignatureRequest(api: XmtpApiClient, signatureRequest: FfiSigna errorHandler: FfiConverterTypeGenericError_lift ) } -public func connectToBackend(host: String, isSecure: Bool, appVersion: String?)async throws -> XmtpApiClient { +/** + * connect to the XMTP backend + * specifying `gateway_host` enables the D14n backend + * and assumes `host` is set to the correct + * d14n backend url. + */ +public func connectToBackend(v3Host: String, gatewayHost: String?, isSecure: Bool, appVersion: String?)async throws -> XmtpApiClient { return try await uniffiRustCallAsync( rustFutureFunc: { - uniffi_xmtpv3_fn_func_connect_to_backend(FfiConverterString.lower(host),FfiConverterBool.lower(isSecure),FfiConverterOptionString.lower(appVersion) + uniffi_xmtpv3_fn_func_connect_to_backend(FfiConverterString.lower(v3Host),FfiConverterOptionString.lower(gatewayHost),FfiConverterBool.lower(isSecure),FfiConverterOptionString.lower(appVersion) ) }, pollFunc: ffi_xmtpv3_rust_future_poll_pointer, @@ -13525,19 +13721,15 @@ public func isConnected(api: XmtpApiClient)async -> Bool { /** * * Static revoke a list of installations */ -public func revokeInstallations(api: XmtpApiClient, recoveryIdentifier: FfiIdentifier, inboxId: String, installationIds: [Data])async throws -> FfiSignatureRequest { - return - try await uniffiRustCallAsync( - rustFutureFunc: { - uniffi_xmtpv3_fn_func_revoke_installations(FfiConverterTypeXmtpApiClient_lower(api),FfiConverterTypeFfiIdentifier_lower(recoveryIdentifier),FfiConverterString.lower(inboxId),FfiConverterSequenceData.lower(installationIds) - ) - }, - pollFunc: ffi_xmtpv3_rust_future_poll_pointer, - completeFunc: ffi_xmtpv3_rust_future_complete_pointer, - freeFunc: ffi_xmtpv3_rust_future_free_pointer, - liftFunc: FfiConverterTypeFfiSignatureRequest_lift, - errorHandler: FfiConverterTypeGenericError_lift - ) +public func revokeInstallations(api: XmtpApiClient, recoveryIdentifier: FfiIdentifier, inboxId: String, installationIds: [Data])throws -> FfiSignatureRequest { + return try FfiConverterTypeFfiSignatureRequest_lift(try rustCallWithError(FfiConverterTypeGenericError_lift) { + uniffi_xmtpv3_fn_func_revoke_installations( + FfiConverterTypeXmtpApiClient_lower(api), + FfiConverterTypeFfiIdentifier_lower(recoveryIdentifier), + FfiConverterString.lower(inboxId), + FfiConverterSequenceData.lower(installationIds),$0 + ) +}) } private enum InitializationResult { @@ -13558,7 +13750,7 @@ private let initializationResult: InitializationResult = { if (uniffi_xmtpv3_checksum_func_apply_signature_request() != 65134) { return InitializationResult.apiChecksumMismatch } - if (uniffi_xmtpv3_checksum_func_connect_to_backend() != 56931) { + if (uniffi_xmtpv3_checksum_func_connect_to_backend() != 13098) { return InitializationResult.apiChecksumMismatch } if (uniffi_xmtpv3_checksum_func_create_client() != 18591) { @@ -13642,7 +13834,7 @@ private let initializationResult: InitializationResult = { if (uniffi_xmtpv3_checksum_func_is_connected() != 17295) { return InitializationResult.apiChecksumMismatch } - if (uniffi_xmtpv3_checksum_func_revoke_installations() != 23629) { + if (uniffi_xmtpv3_checksum_func_revoke_installations() != 39546) { return InitializationResult.apiChecksumMismatch } if (uniffi_xmtpv3_checksum_method_fficonsentcallback_on_consent_update() != 12532) { @@ -13765,10 +13957,10 @@ private let initializationResult: InitializationResult = { if (uniffi_xmtpv3_checksum_method_fficonversation_remove_super_admin() != 46017) { return InitializationResult.apiChecksumMismatch } - if (uniffi_xmtpv3_checksum_method_fficonversation_send() != 7954) { + if (uniffi_xmtpv3_checksum_method_fficonversation_send() != 12477) { return InitializationResult.apiChecksumMismatch } - if (uniffi_xmtpv3_checksum_method_fficonversation_send_optimistic() != 5885) { + if (uniffi_xmtpv3_checksum_method_fficonversation_send_optimistic() != 22242) { return InitializationResult.apiChecksumMismatch } if (uniffi_xmtpv3_checksum_method_fficonversation_send_text() != 55684) { @@ -13972,7 +14164,7 @@ private let initializationResult: InitializationResult = { if (uniffi_xmtpv3_checksum_method_ffisignaturerequest_is_ready() != 65051) { return InitializationResult.apiChecksumMismatch } - if (uniffi_xmtpv3_checksum_method_ffisignaturerequest_missing_address_signatures() != 34688) { + if (uniffi_xmtpv3_checksum_method_ffisignaturerequest_missing_address_signatures() != 55383) { return InitializationResult.apiChecksumMismatch } if (uniffi_xmtpv3_checksum_method_ffisignaturerequest_signature_text() != 60472) { @@ -14035,6 +14227,9 @@ private let initializationResult: InitializationResult = { if (uniffi_xmtpv3_checksum_method_ffixmtpclient_db_reconnect() != 6707) { return InitializationResult.apiChecksumMismatch } + if (uniffi_xmtpv3_checksum_method_ffixmtpclient_delete_message() != 34289) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_xmtpv3_checksum_method_ffixmtpclient_dm_conversation() != 23917) { return InitializationResult.apiChecksumMismatch } diff --git a/Sources/XMTPiOS/MessageVisibilityOptions.swift b/Sources/XMTPiOS/MessageVisibilityOptions.swift new file mode 100644 index 00000000..5e70bf1a --- /dev/null +++ b/Sources/XMTPiOS/MessageVisibilityOptions.swift @@ -0,0 +1,26 @@ +// +// MessageVisibilityOptions.swift +// +// +// Created by XMTP on 1/15/25. +// + +import Foundation + +/// Options that control the visibility and notification behavior of a message +public struct MessageVisibilityOptions { + /// Whether this message should trigger a push notification + public var shouldPush: Bool + + /// Creates message visibility options + /// - Parameter shouldPush: Whether this message should trigger a push notification (default: true) + public init(shouldPush: Bool = true) { + self.shouldPush = shouldPush + } + + /// Converts the visibility options to FFI send message options + /// - Returns: FfiSendMessageOpts instance with the appropriate settings + public func toFfi() -> FfiSendMessageOpts { + FfiSendMessageOpts(shouldPush: shouldPush) + } +} \ No newline at end of file diff --git a/Tests/XMTPTests/ClientTests.swift b/Tests/XMTPTests/ClientTests.swift index 97e0c3e5..b3ec1ebc 100644 --- a/Tests/XMTPTests/ClientTests.swift +++ b/Tests/XMTPTests/ClientTests.swift @@ -12,6 +12,7 @@ class ClientTests: XCTestCase { api: ClientOptions.Api( env: XMTPEnvironment.local, isSecure: false, appVersion: "Testing/0.0.0"), + dbEncryptionKey: key ) let fakeWallet = try PrivateKey.generate() @@ -340,6 +341,35 @@ class ClientTests: XCTestCase { XCTAssertEqual(alixClient2.inboxID, alixClient.inboxID) try alixClient.deleteLocalDatabase() } + + func testCreatesAD14NStagingClient() async throws { + let key = try Crypto.secureRandomBytes(count: 32) + let alix = try PrivateKey.generate() + let options = ClientOptions.init( + api: .init(env: .dev, isSecure: true, gatewayUrl: "https://payer.testnet-staging.xmtp.network:443"), + dbEncryptionKey: key + ) + + let inboxId = try await Client.getOrCreateInboxId( + api: options.api, publicIdentity: alix.identity) + let alixClient = try await Client.create( + account: alix, + options: options + ) + + XCTAssertEqual(inboxId, alixClient.inboxID) + + let alixClient2 = try await Client.build( + publicIdentity: alix.identity, + options: options + ) + + XCTAssertEqual( + alixClient2.publicIdentity.identifier, + alixClient.publicIdentity.identifier) + XCTAssertEqual(alixClient2.inboxID, alixClient.inboxID) + try alixClient.deleteLocalDatabase() + } func testRevokeInstallations() async throws { let key = try Crypto.secureRandomBytes(count: 32) diff --git a/XMTP.podspec b/XMTP.podspec index d8008fc9..e784a59d 100644 --- a/XMTP.podspec +++ b/XMTP.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = "XMTP" - spec.version = "4.5.6" + spec.version = "4.6.0-dev" spec.summary = "XMTP SDK Cocoapod"