Skip to content

Commit 0f50f3b

Browse files
committed
PubNub Swift Chat SDK 0.11.0 release
1 parent f6e8397 commit 0f50f3b

File tree

10 files changed

+259
-9
lines changed

10 files changed

+259
-9
lines changed

PubNubSwiftChatSDK.xcodeproj/project.pbxproj

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
3D043A7A2CA6AAA000F91C05 /* ThreadChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D043A792CA6AAA000F91C05 /* ThreadChannel.swift */; };
1111
3D043A7C2CAAABBD00F91C05 /* ThreadMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D043A7B2CAAABBD00F91C05 /* ThreadMessage.swift */; };
1212
3D043A7E2CAC190200F91C05 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D043A7D2CAC190200F91C05 /* Constants.swift */; };
13+
3D0F90CD2D479A6600986686 /* MutedUsersManagerInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D0F90CC2D479A6600986686 /* MutedUsersManagerInterface.swift */; };
1314
3D2CA2362C5B9320008D2284 /* PubNubChat+Transform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2CA2352C5B9320008D2284 /* PubNubChat+Transform.swift */; };
1415
3D2CA2382C5B9ACA008D2284 /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2CA2372C5B9ACA008D2284 /* File.swift */; };
1516
3D2CA23A2C5B9CF6008D2284 /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2CA2392C5B9CF6008D2284 /* Dictionary.swift */; };
@@ -111,6 +112,7 @@
111112
3D043A792CA6AAA000F91C05 /* ThreadChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadChannel.swift; sourceTree = "<group>"; };
112113
3D043A7B2CAAABBD00F91C05 /* ThreadMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadMessage.swift; sourceTree = "<group>"; };
113114
3D043A7D2CAC190200F91C05 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
115+
3D0F90CC2D479A6600986686 /* MutedUsersManagerInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutedUsersManagerInterface.swift; sourceTree = "<group>"; };
114116
3D1C44A52C918A2200E68446 /* PubNubSwiftChatSDK_Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = PubNubSwiftChatSDK_Info.plist; sourceTree = "<group>"; };
115117
3D2CA2352C5B9320008D2284 /* PubNubChat+Transform.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PubNubChat+Transform.swift"; sourceTree = "<group>"; };
116118
3D2CA2372C5B9ACA008D2284 /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = "<group>"; };
@@ -208,6 +210,14 @@
208210
/* End PBXFrameworksBuildPhase section */
209211

210212
/* Begin PBXGroup section */
213+
3D0F90CB2D479A5700986686 /* MutedUsers */ = {
214+
isa = PBXGroup;
215+
children = (
216+
3D0F90CC2D479A6600986686 /* MutedUsersManagerInterface.swift */,
217+
);
218+
path = MutedUsers;
219+
sourceTree = "<group>";
220+
};
211221
3D9A17922CC156F100F3F8AB /* MessageDraft */ = {
212222
isa = PBXGroup;
213223
children = (
@@ -280,6 +290,7 @@
280290
3DB73A252C4FE1F6007FE249 /* Chat.swift */,
281291
3DB73A7A2C57EA29007FE249 /* ChatImpl.swift */,
282292
3DB73A242C4FE1F6007FE249 /* ChatConfiguration.swift */,
293+
3D0F90CB2D479A5700986686 /* MutedUsers */,
283294
3D9A17922CC156F100F3F8AB /* MessageDraft */,
284295
3DB73A432C511D36007FE249 /* Models */,
285296
3DB73A3A2C50F415007FE249 /* Entities */,
@@ -514,6 +525,7 @@
514525
3DB73A632C579938007FE249 /* PubNub.ObjectSortField.swift in Sources */,
515526
3DB73A732C57CE9E007FE249 /* PubNubChat.RestrictionType.swift in Sources */,
516527
3DB73A262C4FE1F6007FE249 /* ChatConfiguration.swift in Sources */,
528+
3D0F90CD2D479A6600986686 /* MutedUsersManagerInterface.swift in Sources */,
517529
3D2CA2482C621876008D2284 /* MessageActionType.swift in Sources */,
518530
3DB73A272C4FE1F6007FE249 /* Chat.swift in Sources */,
519531
3DB73A472C51353B007FE249 /* MessageMentionedUser.swift in Sources */,
@@ -886,15 +898,15 @@
886898
repositoryURL = "https://github.com/pubnub/kmp-chat/";
887899
requirement = {
888900
kind = exactVersion;
889-
version = "0.10.1-dev";
901+
version = "0.11.0-dev";
890902
};
891903
};
892904
3DCF7DFA2CD0FFCC00889326 /* XCRemoteSwiftPackageReference "swift" */ = {
893905
isa = XCRemoteSwiftPackageReference;
894906
repositoryURL = "https://github.com/pubnub/swift";
895907
requirement = {
896908
kind = exactVersion;
897-
version = 8.2.4;
909+
version = 8.3.0;
898910
};
899911
};
900912
/* End XCRemoteSwiftPackageReference section */

Sources/Chat.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ public protocol Chat: AnyObject {
4242
/// 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"
4343
var reactionsActionName: String { get }
4444

45+
/// An object for manipulating the list of muted users.
46+
///
47+
/// 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
48+
/// using App Context for the current user.
49+
///
50+
/// Please note that this is not a server-side moderation mechanism, but rather a way to ignore messages from certain users on the client.
51+
var mutedUsersManager: MutedUsersManagerInterface { get }
52+
4553
/// Initializes the current instance and performs any necessary setup.
4654
///
4755
/// This method must be called before invoking any other operations

Sources/ChatConfiguration.swift

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,23 @@ public struct ChatConfiguration {
172172
/// 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.
173173
/// It also lets you configure your own message actions whenever a message is edited or deleted
174174
public var customPayloads: CustomPayloads?
175+
/// Enable automatic syncing of the ``MutedUsersManager`` data with App Context, using the current `userId` as the key.
176+
///
177+
/// Specifically, the data is saved in the `custom` object of the following User in App Context:
178+
///
179+
/// ```
180+
/// PN_PRIV.{userId}.mute.1
181+
/// ```
182+
///
183+
/// where `{userId}` is the current PubNubConfiguration's `userId`
184+
///
185+
/// If using Access Manager, the access token must be configured with the appropriate rights to subscribe to that
186+
/// channel, and get, update, and delete the App Context User with that id.
187+
///
188+
/// Due to App Context size limits, the number of muted users is limited to around 200 and will result in sync errors
189+
/// when the limit is exceeded. The list will not sync until its size is reduced.
190+
///
191+
public var syncMutedUsers: Bool
175192

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

207227
func transform() -> any PubNubChat.ChatConfiguration {
@@ -219,7 +239,8 @@ public struct ChatConfiguration {
219239
),
220240
rateLimitFactor: Int32(rateLimitFactor),
221241
rateLimitPerChannel: rateLimitPerChannel.transform(),
222-
customPayloads: customPayloads?.transform()
242+
customPayloads: customPayloads?.transform(),
243+
syncMutedUsers: syncMutedUsers
223244
)
224245
}
225246
}

Sources/ChatImpl.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,9 @@ import PubNubSDK
2020
/// This class inherits all the documentation for methods defined in the ``Chat`` protocol.
2121
/// Refer to the ``Chat`` protocol for details on how individual methods work.
2222
public final class ChatImpl {
23-
/// Allows you to access any Swift SDK method. For example, if you want to call a method available in the
24-
/// App Context API, you'd use `pubNub.allUUIDMetadata(include:filter:sort:limit:page:custom:completion)`
2523
public let pubNub: PubNub
26-
/// Contains chat app configuration settings, such as ``LogLevel`` or typing timeout
27-
/// that you can provide when initializing your chat app with the init method
2824
public let config: ChatConfiguration
25+
public let mutedUsersManager: any MutedUsersManagerInterface
2926

3027
let chat: PubNubChat.ChatImpl
3128

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

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

5554
pubNub.setConsumer(identifier: "chat-sdk", value: "CA-SWIFT/\(pubNubSwiftChatSDKVersion)")
5655
// Creates an association between KMP chat and the current instance

Sources/Entities/User.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public protocol User {
3535
var type: String? { get }
3636
/// The last updated timestamp for the object
3737
var updated: String? { get }
38+
/// 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.
39+
var eTag: String? { get }
3840
/// Timestamp for the last time the user information was updated or modified
3941
var lastActiveTimestamp: TimeInterval? { get }
4042
/// Indicates whether the user is currently (at the time of obtaining this ``User`` object) active
@@ -75,6 +77,24 @@ public protocol User {
7577
completion: ((Swift.Result<ChatType.ChatUserType, Error>) -> Void)?
7678
)
7779

80+
/// Updates the metadata of the user with information provided in `updateAction`
81+
///
82+
/// Please note that `updateAction` will be called **at least** once with the current data from the `User` object in
83+
/// the argument. Inside `updateAction`, new values for `User` fields should be computed and returned as a closure result.
84+
///
85+
/// In case the user's information has changed on the server since the original User object was retrieved, the `updateAction` will be called again
86+
/// 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.
87+
///
88+
/// - Parameters:
89+
/// - updateAction: A function for computing new values for the `User` fields based on the provided `User` argument and returning changes to apply
90+
/// - completion: The async `Result` of the method call
91+
/// - **Success**: The updated user object with its metadata
92+
/// - **Failure**: An `Error` describing the failure
93+
func update(
94+
updateAction: @escaping (ChatType.ChatUserType) -> [PubNubMetadataChange<PubNubUserMetadata>],
95+
completion: ((Swift.Result<UserImpl, Error>) -> Void)?
96+
)
97+
7898
/// Deletes the user. If soft deletion is enabled, the user's data is retained but marked as inactive.
7999
///
80100
/// - Parameters:

Sources/Entities/UserImpl.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public final class UserImpl {
3434
status: String? = nil,
3535
type: String? = nil,
3636
updated: String? = nil,
37+
eTag: String? = nil,
3738
lastActiveTimestamp: Timetoken? = nil
3839
) {
3940
let underlyingUser = PubNubChat.UserImpl(
@@ -47,6 +48,7 @@ public final class UserImpl {
4748
status: status,
4849
type: type,
4950
updated: updated,
51+
eTag: eTag,
5052
lastActiveTimestamp: lastActiveTimestamp?.asKotlinLong()
5153
)
5254
self.init(
@@ -78,6 +80,7 @@ extension UserImpl: User {
7880
public var status: String? { user.status }
7981
public var type: String? { user.type }
8082
public var updated: String? { user.updated }
83+
public var eTag: String? { user.eTag }
8184
public var lastActiveTimestamp: TimeInterval? { user.lastActiveTimestamp?.doubleValue }
8285
public var active: Bool { user.active }
8386

@@ -121,6 +124,47 @@ extension UserImpl: User {
121124
}
122125
}
123126

127+
// swiftlint:disable:next cyclomatic_complexity
128+
public func update(
129+
updateAction: @escaping (UserImpl) -> [PubNubMetadataChange<PubNubUserMetadata>],
130+
completion: ((Swift.Result<UserImpl, Error>) -> Void)? = nil
131+
) {
132+
user.update(updateAction: { values, user in
133+
for change in updateAction(UserImpl(user: user)) {
134+
switch change {
135+
case let .stringOptional(keyPath, value):
136+
switch keyPath {
137+
case \.name:
138+
values.name = value
139+
case \.type:
140+
values.type = value
141+
case \.status:
142+
values.status = value
143+
case \.externalId:
144+
values.externalId = value
145+
case \.profileURL:
146+
values.profileUrl = value
147+
case \.email:
148+
values.email = value
149+
default:
150+
break
151+
}
152+
case let .customOptional(key, value):
153+
if key == \.custom {
154+
values.custom = value
155+
}
156+
}
157+
}
158+
}).async(caller: self) { (result: FutureResult<UserImpl, PubNubChat.User>) in
159+
switch result.result {
160+
case let .success(user):
161+
completion?(.success(UserImpl(user: user)))
162+
case let .failure(error):
163+
completion?(.failure(error))
164+
}
165+
}
166+
}
167+
124168
public func delete(
125169
soft: Bool = false,
126170
completion: ((Swift.Result<UserImpl?, Error>) -> Void)? = nil
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//
2+
// MutedUsersManagerInterface.swift
3+
//
4+
// Copyright (c) PubNub Inc.
5+
// All rights reserved.
6+
//
7+
// This source code is licensed under the license found in the
8+
// LICENSE file in the root directory of this source tree.
9+
//
10+
11+
import Foundation
12+
import PubNubChat
13+
14+
/// Defines a protocol for an object capable of muting and unmuting users.
15+
public protocol MutedUsersManagerInterface {
16+
/// The current set of muted users.
17+
var mutedUsers: Set<String> { get }
18+
19+
/// Add a user to the list of muted users
20+
///
21+
/// - Parameters:
22+
/// - userId: The ID of the user to mute
23+
/// - completion: The `Result` of an asynchronous call:
24+
/// - **Success**: The operation succeeded, returns nothing
25+
/// - **Failure**: An `Error` describing the failure
26+
func muteUser(userId: String, completion: ((Swift.Result<Void, Error>) -> Void)?)
27+
28+
/// Removes a user from the list of muted users
29+
///
30+
/// - Parameters:
31+
/// - userId: The ID of the muted user
32+
/// - completion: The `Result` of an asynchronous call:
33+
/// - **Success**: The operation succeeded, returns nothing
34+
/// - **Failure**: An `Error` describing the failure
35+
func unmuteUser(userId: String, completion: ((Swift.Result<Void, Error>) -> Void)?)
36+
}
37+
38+
/// Provides a default implementation for ``MutedUsersManagerInterface`` methods requiring a completion handler,
39+
/// allowing the completion handler to be `nil` by default. This simplifies usage for cases where no action is needed after the method completes.
40+
extension MutedUsersManagerInterface {
41+
/// Add a user to the list of muted users
42+
///
43+
/// - Parameter userId: The ID of the user to mute
44+
func muteUser(userId: String) {
45+
muteUser(userId: userId, completion: nil)
46+
}
47+
48+
/// Removes a user from the list of muted users
49+
///
50+
/// - Parameter userId: The ID of the muted user
51+
func unmuteUser(userId: String) {
52+
unmuteUser(userId: userId, completion: nil)
53+
}
54+
}
55+
56+
class MutedUsersManagerImpl: MutedUsersManagerInterface {
57+
let underlying: PubNubChat.MutedUsersManager
58+
59+
init(underlying: PubNubChat.MutedUsersManager) {
60+
self.underlying = underlying
61+
}
62+
63+
var mutedUsers: Set<String> {
64+
underlying.mutedUsers
65+
}
66+
67+
func muteUser(userId: String, completion: ((Swift.Result<Void, Error>) -> Void)? = nil) {
68+
underlying.muteUser(userId: userId).async(
69+
caller: self
70+
) { (result: FutureResult<MutedUsersManagerImpl, PubNubChat.KotlinUnit>) in
71+
switch result.result {
72+
case .success:
73+
completion?(.success(()))
74+
case let .failure(error):
75+
completion?(.failure(error))
76+
}
77+
}
78+
}
79+
80+
func unmuteUser(userId: String, completion: ((Swift.Result<Void, Error>) -> Void)? = nil) {
81+
underlying.unmuteUser(userId: userId).async(
82+
caller: self
83+
) { (result: FutureResult<MutedUsersManagerImpl, PubNubChat.KotlinUnit>) in
84+
switch result.result {
85+
case .success:
86+
completion?(.success(()))
87+
case let .failure(error):
88+
completion?(.failure(error))
89+
}
90+
}
91+
}
92+
}

Tests/ChannelIntegrationTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ class ChannelIntegrationTests: PubNubSwiftChatSDKIntegrationTests {
135135
try awaitResult { chat.deleteChannel(
136136
id: anotherChannel.id,
137137
completion: $0
138-
)}
138+
) }
139139
}
140140
}
141141

0 commit comments

Comments
 (0)