Skip to content

Commit

Permalink
PubNub Swift Chat SDK 0.11.0 release
Browse files Browse the repository at this point in the history
  • Loading branch information
jguz-pubnub committed Jan 27, 2025
1 parent f6e8397 commit 8fe0e9c
Show file tree
Hide file tree
Showing 11 changed files with 266 additions and 9 deletions.
16 changes: 14 additions & 2 deletions PubNubSwiftChatSDK.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
3D043A7A2CA6AAA000F91C05 /* ThreadChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D043A792CA6AAA000F91C05 /* ThreadChannel.swift */; };
3D043A7C2CAAABBD00F91C05 /* ThreadMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D043A7B2CAAABBD00F91C05 /* ThreadMessage.swift */; };
3D043A7E2CAC190200F91C05 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D043A7D2CAC190200F91C05 /* Constants.swift */; };
3D0F90CD2D479A6600986686 /* MutedUsersManagerInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D0F90CC2D479A6600986686 /* MutedUsersManagerInterface.swift */; };
3D2CA2362C5B9320008D2284 /* PubNubChat+Transform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2CA2352C5B9320008D2284 /* PubNubChat+Transform.swift */; };
3D2CA2382C5B9ACA008D2284 /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2CA2372C5B9ACA008D2284 /* File.swift */; };
3D2CA23A2C5B9CF6008D2284 /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2CA2392C5B9CF6008D2284 /* Dictionary.swift */; };
Expand Down Expand Up @@ -111,6 +112,7 @@
3D043A792CA6AAA000F91C05 /* ThreadChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadChannel.swift; sourceTree = "<group>"; };
3D043A7B2CAAABBD00F91C05 /* ThreadMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadMessage.swift; sourceTree = "<group>"; };
3D043A7D2CAC190200F91C05 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
3D0F90CC2D479A6600986686 /* MutedUsersManagerInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutedUsersManagerInterface.swift; sourceTree = "<group>"; };
3D1C44A52C918A2200E68446 /* PubNubSwiftChatSDK_Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = PubNubSwiftChatSDK_Info.plist; sourceTree = "<group>"; };
3D2CA2352C5B9320008D2284 /* PubNubChat+Transform.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PubNubChat+Transform.swift"; sourceTree = "<group>"; };
3D2CA2372C5B9ACA008D2284 /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -208,6 +210,14 @@
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
3D0F90CB2D479A5700986686 /* MutedUsers */ = {
isa = PBXGroup;
children = (
3D0F90CC2D479A6600986686 /* MutedUsersManagerInterface.swift */,
);
path = MutedUsers;
sourceTree = "<group>";
};
3D9A17922CC156F100F3F8AB /* MessageDraft */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -280,6 +290,7 @@
3DB73A252C4FE1F6007FE249 /* Chat.swift */,
3DB73A7A2C57EA29007FE249 /* ChatImpl.swift */,
3DB73A242C4FE1F6007FE249 /* ChatConfiguration.swift */,
3D0F90CB2D479A5700986686 /* MutedUsers */,
3D9A17922CC156F100F3F8AB /* MessageDraft */,
3DB73A432C511D36007FE249 /* Models */,
3DB73A3A2C50F415007FE249 /* Entities */,
Expand Down Expand Up @@ -514,6 +525,7 @@
3DB73A632C579938007FE249 /* PubNub.ObjectSortField.swift in Sources */,
3DB73A732C57CE9E007FE249 /* PubNubChat.RestrictionType.swift in Sources */,
3DB73A262C4FE1F6007FE249 /* ChatConfiguration.swift in Sources */,
3D0F90CD2D479A6600986686 /* MutedUsersManagerInterface.swift in Sources */,
3D2CA2482C621876008D2284 /* MessageActionType.swift in Sources */,
3DB73A272C4FE1F6007FE249 /* Chat.swift in Sources */,
3DB73A472C51353B007FE249 /* MessageMentionedUser.swift in Sources */,
Expand Down Expand Up @@ -886,15 +898,15 @@
repositoryURL = "https://github.com/pubnub/kmp-chat/";
requirement = {
kind = exactVersion;
version = "0.10.1-dev";
version = "0.11.0-dev";
};
};
3DCF7DFA2CD0FFCC00889326 /* XCRemoteSwiftPackageReference "swift" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/pubnub/swift";
requirement = {
kind = exactVersion;
version = 8.2.4;
version = 8.3.0;
};
};
/* End XCRemoteSwiftPackageReference section */
Expand Down
8 changes: 8 additions & 0 deletions Sources/Chat.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ public protocol Chat: AnyObject {
/// A type of action you added to your Message object whenever a reaction is added to a published message, like "reacted". The default value is "reactions"
var reactionsActionName: String { get }

/// An object for manipulating the list of muted users.
///
/// The list is local to this instance of Chat (it is not persisted anywhere) unless ``ChatConfiguration/syncMutedUsers`` is enabled, in which case it will be synced
/// using App Context for the current user.
///
/// Please note that this is not a server-side moderation mechanism, but rather a way to ignore messages from certain users on the client.
var mutedUsersManager: MutedUsersManagerInterface { get }

/// Initializes the current instance and performs any necessary setup.
///
/// This method must be called before invoking any other operations
Expand Down
25 changes: 23 additions & 2 deletions Sources/ChatConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,23 @@ public struct ChatConfiguration {
/// Property that lets you define your custom message payload to be sent and/or received by Chat SDK on one or all channels, whenever it differs from the default `message.text` Chat SDK payload.
/// It also lets you configure your own message actions whenever a message is edited or deleted
public var customPayloads: CustomPayloads?
/// Enable automatic syncing of the ``MutedUsersManager`` data with App Context, using the current `userId` as the key.
///
/// Specifically, the data is saved in the `custom` object of the following User in App Context:
///
/// ```
/// PN_PRIV.{userId}.mute.1
/// ```
///
/// where `{userId}` is the current PubNubConfiguration's `userId`
///
/// If using Access Manager, the access token must be configured with the appropriate rights to subscribe to that
/// channel, and get, update, and delete the App Context User with that id.
///
/// Due to App Context size limits, the number of muted users is limited to around 200 and will result in sync errors
/// when the limit is exceeded. The list will not sync until its size is reduced.
///
public var syncMutedUsers: Bool

/// Creates a new ``ChatConfiguration`` object
///
Expand All @@ -184,6 +201,7 @@ public struct ChatConfiguration {
/// - rateLimitFactor: The so-called "exponential backoff" which multiplicatively decreases the rate at which messages are published on channels
/// - rateLimitPerChannel: Client-side limit that states the rate at which messages can be published on a given channel type
/// - customPayloads: Custom message payload to be sent and/or received by Chat SDK
/// - syncMutedUsers: A boolean value that controls syncing of muted users
public init(
logLevel: LogLevel = .off,
typingTimeout: Int = 5,
Expand All @@ -192,7 +210,8 @@ public struct ChatConfiguration {
pushNotificationsConfig: PushNotificationsConfig = .init(),
rateLimitFactor: Int = 2,
rateLimitPerChannel: [ChannelType: Int64] = ChannelType.allCases.reduce(into: [ChannelType: Int64]()) { res, type in res[type] = 0 },
customPayloads: CustomPayloads? = nil
customPayloads: CustomPayloads? = nil,
syncMutedUsers: Bool = false
) {
self.logLevel = logLevel
self.typingTimeout = typingTimeout
Expand All @@ -202,6 +221,7 @@ public struct ChatConfiguration {
self.rateLimitFactor = rateLimitFactor
self.rateLimitPerChannel = rateLimitPerChannel
self.customPayloads = customPayloads
self.syncMutedUsers = syncMutedUsers
}

func transform() -> any PubNubChat.ChatConfiguration {
Expand All @@ -219,7 +239,8 @@ public struct ChatConfiguration {
),
rateLimitFactor: Int32(rateLimitFactor),
rateLimitPerChannel: rateLimitPerChannel.transform(),
customPayloads: customPayloads?.transform()
customPayloads: customPayloads?.transform(),
syncMutedUsers: syncMutedUsers
)
}
}
Expand Down
7 changes: 3 additions & 4 deletions Sources/ChatImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,9 @@ import PubNubSDK
/// This class inherits all the documentation for methods defined in the ``Chat`` protocol.
/// Refer to the ``Chat`` protocol for details on how individual methods work.
public final class ChatImpl {
/// Allows you to access any Swift SDK method. For example, if you want to call a method available in the
/// App Context API, you'd use `pubNub.allUUIDMetadata(include:filter:sort:limit:page:custom:completion)`
public let pubNub: PubNub
/// Contains chat app configuration settings, such as ``LogLevel`` or typing timeout
/// that you can provide when initializing your chat app with the init method
public let config: ChatConfiguration
public let mutedUsersManager: any MutedUsersManagerInterface

let chat: PubNubChat.ChatImpl

Expand All @@ -41,6 +38,7 @@ public final class ChatImpl {
pubNub = PubNub(configuration: pubNubConfiguration)
config = chatConfiguration
chat = ChatImpl.createKMPChat(from: pubNub, config: chatConfiguration)
mutedUsersManager = MutedUsersManagerImpl(underlying: chat.mutedUsersManager)

pubNub.setConsumer(identifier: "chat-sdk", value: "CA-SWIFT/\(pubNubSwiftChatSDKVersion)")
// Creates an association between KMP chat and the current instance
Expand All @@ -51,6 +49,7 @@ public final class ChatImpl {
self.pubNub = pubNub
config = configuration
chat = ChatImpl.createKMPChat(from: pubNub, config: configuration)
mutedUsersManager = MutedUsersManagerImpl(underlying: chat.mutedUsersManager)

pubNub.setConsumer(identifier: "chat-sdk", value: "CA-SWIFT/\(pubNubSwiftChatSDKVersion)")
// Creates an association between KMP chat and the current instance
Expand Down
20 changes: 20 additions & 0 deletions Sources/Entities/User.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public protocol User {
var type: String? { get }
/// The last updated timestamp for the object
var updated: String? { get }
/// The entity tag (`ETag`) that was returned by the server with this User object. It is a random string that changes with each data update.
var eTag: String? { get }
/// Timestamp for the last time the user information was updated or modified
var lastActiveTimestamp: TimeInterval? { get }
/// Indicates whether the user is currently (at the time of obtaining this ``User`` object) active
Expand Down Expand Up @@ -75,6 +77,24 @@ public protocol User {
completion: ((Swift.Result<ChatType.ChatUserType, Error>) -> Void)?
)

/// Updates the metadata of the user with information provided in `updateAction`
///
/// Please note that `updateAction` will be called **at least** once with the current data from the `User` object in
/// the argument. Inside `updateAction`, new values for `User` fields should be computed and returned as a closure result.
///
/// In case the user's information has changed on the server since the original User object was retrieved, the `updateAction` will be called again
/// with new User data that represents the current server state. This might happen multiple times until either new data is saved successfully, or the request fails.
///
/// - Parameters:
/// - updateAction: A function for computing new values for the `User` fields based on the provided `User` argument and returning changes to apply
/// - completion: The async `Result` of the method call
/// - **Success**: The updated user object with its metadata
/// - **Failure**: An `Error` describing the failure
func update(
updateAction: @escaping (ChatType.ChatUserType) -> [PubNubMetadataChange<PubNubUserMetadata>],
completion: ((Swift.Result<UserImpl, Error>) -> Void)?
)

/// Deletes the user. If soft deletion is enabled, the user's data is retained but marked as inactive.
///
/// - Parameters:
Expand Down
44 changes: 44 additions & 0 deletions Sources/Entities/UserImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public final class UserImpl {
status: String? = nil,
type: String? = nil,
updated: String? = nil,
eTag: String? = nil,
lastActiveTimestamp: Timetoken? = nil
) {
let underlyingUser = PubNubChat.UserImpl(
Expand All @@ -47,6 +48,7 @@ public final class UserImpl {
status: status,
type: type,
updated: updated,
eTag: eTag,
lastActiveTimestamp: lastActiveTimestamp?.asKotlinLong()
)
self.init(
Expand Down Expand Up @@ -78,6 +80,7 @@ extension UserImpl: User {
public var status: String? { user.status }
public var type: String? { user.type }
public var updated: String? { user.updated }
public var eTag: String? { user.eTag }
public var lastActiveTimestamp: TimeInterval? { user.lastActiveTimestamp?.doubleValue }
public var active: Bool { user.active }

Expand Down Expand Up @@ -121,6 +124,47 @@ extension UserImpl: User {
}
}

// swiftlint:disable:next cyclomatic_complexity
public func update(
updateAction: @escaping (UserImpl) -> [PubNubMetadataChange<PubNubUserMetadata>],
completion: ((Swift.Result<UserImpl, Error>) -> Void)? = nil
) {
user.update(updateAction: { values, user in
for change in updateAction(UserImpl(user: user)) {
switch change {
case let .stringOptional(keyPath, value):
switch keyPath {
case \.name:
values.name = value
case \.type:
values.type = value
case \.status:
values.status = value
case \.externalId:
values.externalId = value
case \.profileURL:
values.profileUrl = value
case \.email:
values.email = value
default:
break
}
case let .customOptional(key, value):
if key == \.custom {
values.custom = value
}
}
}
}).async(caller: self) { (result: FutureResult<UserImpl, PubNubChat.User>) in
switch result.result {
case let .success(user):
completion?(.success(UserImpl(user: user)))
case let .failure(error):
completion?(.failure(error))
}
}
}

public func delete(
soft: Bool = false,
completion: ((Swift.Result<UserImpl?, Error>) -> Void)? = nil
Expand Down
7 changes: 7 additions & 0 deletions Sources/Models/QuotedMessage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,10 @@ public struct QuotedMessage {
)
}
}

public extension QuotedMessage {
/// Returns an array of `MessageElement`representing plain text or additional information such as user mentions, channel references and links
func getMessageElements() -> [MessageElement] {
transform().getMessageElements().compactMap { MessageElement.from(element: $0) }
}
}
92 changes: 92 additions & 0 deletions Sources/MutedUsers/MutedUsersManagerInterface.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//
// MutedUsersManagerInterface.swift
//
// Copyright (c) PubNub Inc.
// All rights reserved.
//
// This source code is licensed under the license found in the
// LICENSE file in the root directory of this source tree.
//

import Foundation
import PubNubChat

/// Defines a protocol for an object capable of muting and unmuting users.
public protocol MutedUsersManagerInterface {
/// The current set of muted users.
var mutedUsers: Set<String> { get }

/// Add a user to the list of muted users
///
/// - Parameters:
/// - userId: The ID of the user to mute
/// - completion: The `Result` of an asynchronous call:
/// - **Success**: The operation succeeded, returns nothing
/// - **Failure**: An `Error` describing the failure
func muteUser(userId: String, completion: ((Swift.Result<Void, Error>) -> Void)?)

/// Removes a user from the list of muted users
///
/// - Parameters:
/// - userId: The ID of the muted user
/// - completion: The `Result` of an asynchronous call:
/// - **Success**: The operation succeeded, returns nothing
/// - **Failure**: An `Error` describing the failure
func unmuteUser(userId: String, completion: ((Swift.Result<Void, Error>) -> Void)?)
}

/// Provides a default implementation for ``MutedUsersManagerInterface`` methods requiring a completion handler,
/// allowing the completion handler to be `nil` by default. This simplifies usage for cases where no action is needed after the method completes.
extension MutedUsersManagerInterface {
/// Add a user to the list of muted users
///
/// - Parameter userId: The ID of the user to mute
func muteUser(userId: String) {
muteUser(userId: userId, completion: nil)
}

/// Removes a user from the list of muted users
///
/// - Parameter userId: The ID of the muted user
func unmuteUser(userId: String) {
unmuteUser(userId: userId, completion: nil)
}
}

class MutedUsersManagerImpl: MutedUsersManagerInterface {
let underlying: PubNubChat.MutedUsersManager

init(underlying: PubNubChat.MutedUsersManager) {
self.underlying = underlying
}

var mutedUsers: Set<String> {
underlying.mutedUsers
}

func muteUser(userId: String, completion: ((Swift.Result<Void, Error>) -> Void)? = nil) {
underlying.muteUser(userId: userId).async(
caller: self
) { (result: FutureResult<MutedUsersManagerImpl, PubNubChat.KotlinUnit>) in
switch result.result {
case .success:
completion?(.success(()))
case let .failure(error):
completion?(.failure(error))
}
}
}

func unmuteUser(userId: String, completion: ((Swift.Result<Void, Error>) -> Void)? = nil) {
underlying.unmuteUser(userId: userId).async(
caller: self
) { (result: FutureResult<MutedUsersManagerImpl, PubNubChat.KotlinUnit>) in
switch result.result {
case .success:
completion?(.success(()))
case let .failure(error):
completion?(.failure(error))
}
}
}
}
2 changes: 1 addition & 1 deletion Tests/ChannelIntegrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class ChannelIntegrationTests: PubNubSwiftChatSDKIntegrationTests {
try awaitResult { chat.deleteChannel(
id: anotherChannel.id,
completion: $0
)}
) }
}
}

Expand Down
Loading

0 comments on commit 8fe0e9c

Please sign in to comment.