Skip to content

Commit

Permalink
Merge branch 'master' into feat/async-await
Browse files Browse the repository at this point in the history
  • Loading branch information
jguz-pubnub committed Jan 28, 2025
2 parents 80cc744 + 71432b6 commit 21c236a
Show file tree
Hide file tree
Showing 30 changed files with 459 additions and 36 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
needs: check-release
if: needs.check-release.outputs.release == 'true'
runs-on:
group: macos-arm-gh
group: macos-gh
steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
SDK_PAM_PUB_KEY: ${{ secrets.SDK_PAM_PUB_KEY }}
SDK_PAM_SEC_KEY: ${{ secrets.SDK_PAM_SEC_KEY }}
runs-on:
group: macos-arm-gh
group: macos-gh
strategy:
matrix:
environment: [iOS]
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/run-validations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
package-managers-validation:
name: Validate package managers
runs-on:
group: macos-arm-gh
group: macos-gh
strategy:
matrix:
managers: [Swift Package Manager]
Expand Down
28 changes: 25 additions & 3 deletions .pubnub.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
---
name: swift-chat-sdk
scm: github.com/pubnub/swift-chat-sdk
version: "0.10.1"
version: 0.11.0
schema: 1
changelog:
- date: 2025-01-28
version: 0.11.0
changes:
- type: feature
text: "Add the new `update(updateAction:completion:)`method on User entity. This method can be used to update data on the server without losing intermediate updates that might have happened in the time between when the object was last received and updated."
- type: feature
text: "Add the ability to mute and unmute users on the Chat instance. There are `chat.mutedUsersManager.mute(userId:completion:)` and `chat.mutedUsersManager.unmuteUser(userId:completion:)` to mute and unmute a user, respectively."
- type: feature
text: "Add the option to automatically sync the mute list by enabling `ChatConfiguration.syncMutedUsers`."
- type: feature
text: "Add missing function to parse quoted message text into `[MessageElement]`."
- type: bug
text: "Fix the problem of overwriting custom data at regular intervals when `storeUserActivityInterval` is enabled."
- date: 2025-01-23
version: 0.10.3
changes:
- type: bug
text: "Fix the error when retrieving the unread messages count on the Membership instance."
- date: 2025-01-14
version: 0.10.2
changes:
- type: bug
text: "Fix the bug with messages being deleted from Message Persistence."
- date: 2025-01-09
version: 0.10.1
changes:
Expand Down Expand Up @@ -91,7 +113,7 @@ sdks:
- distribution-type: source
distribution-repository: GitHub release
package-name: PubNubSwiftChatSDK
location: https://github.com/pubnub/swift-chat-sdk/archive/refs/tags/0.10.1-dev.zip
location: https://github.com/pubnub/swift-chat-sdk/archive/refs/tags/0.11.0-dev.zip
supported-platforms:
supported-operating-systems:
iOS:
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ let package = Package(
)
],
dependencies: [
.package(url: "https://github.com/pubnub/kmp-chat", exact: "0.10.0-dev"),
.package(url: "https://github.com/pubnub/swift", exact: "8.2.3")
.package(url: "https://github.com/pubnub/kmp-chat", exact: "0.11.0-dev"),
.package(url: "https://github.com/pubnub/swift", exact: "8.3.0")
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
Expand Down
24 changes: 20 additions & 4 deletions PubNubSwiftChatSDK.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
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 */; };
3D0F90D32D48DB4700986686 /* MutedUsersManagerInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D0F90D12D48DB4700986686 /* MutedUsersManagerInterface.swift */; };
3D0F90D52D48DEC700986686 /* MutedUsersManagerInterface+AsyncAwait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D0F90D42D48DEC700986686 /* MutedUsersManagerInterface+AsyncAwait.swift */; };
3D27B8D02D2D9A61003AD459 /* ThreadChannelAsyncIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D27B8CF2D2D9A61003AD459 /* ThreadChannelAsyncIntegrationTests.swift */; };
3D27B8D22D2D9BBB003AD459 /* ThreadChannel+AsyncAwait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D27B8D12D2D9BBB003AD459 /* ThreadChannel+AsyncAwait.swift */; };
3D27B8D42D2EAEA6003AD459 /* MessageDraftAsyncIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D27B8D32D2EAEA5003AD459 /* MessageDraftAsyncIntegrationTests.swift */; };
Expand Down Expand Up @@ -143,6 +145,8 @@
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>"; };
3D0F90D12D48DB4700986686 /* MutedUsersManagerInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutedUsersManagerInterface.swift; sourceTree = "<group>"; };
3D0F90D42D48DEC700986686 /* MutedUsersManagerInterface+AsyncAwait.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MutedUsersManagerInterface+AsyncAwait.swift"; sourceTree = "<group>"; };
3D1C44A52C918A2200E68446 /* PubNubSwiftChatSDK_Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = PubNubSwiftChatSDK_Info.plist; sourceTree = "<group>"; };
3D27B8CF2D2D9A61003AD459 /* ThreadChannelAsyncIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadChannelAsyncIntegrationTests.swift; sourceTree = "<group>"; };
3D27B8D12D2D9BBB003AD459 /* ThreadChannel+AsyncAwait.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadChannel+AsyncAwait.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -270,6 +274,15 @@
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
3D0F90D22D48DB4700986686 /* MutedUsers */ = {
isa = PBXGroup;
children = (
3D0F90D12D48DB4700986686 /* MutedUsersManagerInterface.swift */,
3D0F90D42D48DEC700986686 /* MutedUsersManagerInterface+AsyncAwait.swift */,
);
path = MutedUsers;
sourceTree = "<group>";
};
3D46D42E2D122C30007D08DB /* Common */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -374,6 +387,7 @@
3D46D43C2D12335D007D08DB /* Chat+AsyncAwait.swift */,
3DB73A7A2C57EA29007FE249 /* ChatImpl.swift */,
3DB73A242C4FE1F6007FE249 /* ChatConfiguration.swift */,
3D0F90D22D48DB4700986686 /* MutedUsers */,
3D9A17922CC156F100F3F8AB /* MessageDraft */,
3DB73A432C511D36007FE249 /* Models */,
3DB73A3A2C50F415007FE249 /* Entities */,
Expand Down Expand Up @@ -685,6 +699,7 @@
3DB73A532C53A20C007FE249 /* MessageImpl.swift in Sources */,
3DB73A672C57A8C5007FE249 /* CreateGroupConversationResult.swift in Sources */,
3DB2A8B52C807E2A00167058 /* MembershipImpl.swift in Sources */,
3D0F90D32D48DB4700986686 /* MutedUsersManagerInterface.swift in Sources */,
3DB2A8B92C80993B00167058 /* ChannelType.swift in Sources */,
3D46D4402D130526007D08DB /* Message+AsyncAwait.swift in Sources */,
3DB73A592C53BF18007FE249 /* GetFileItem.swift in Sources */,
Expand All @@ -693,6 +708,7 @@
3DB73A6D2C57BB35007FE249 /* BaseMessage.swift in Sources */,
3DB73A5B2C53CAB1007FE249 /* BaseChannel.swift in Sources */,
3DB73A7F2C58CCAE007FE249 /* GetCurrentUserMentionsResult.swift in Sources */,
3D0F90D52D48DEC700986686 /* MutedUsersManagerInterface+AsyncAwait.swift in Sources */,
3D043A7E2CAC190200F91C05 /* Constants.swift in Sources */,
3D7062C82D2D82A5000729E1 /* User+AsyncAwait.swift in Sources */,
3DB73A3E2C50F5F3007FE249 /* Membership.swift in Sources */,
Expand Down Expand Up @@ -931,7 +947,7 @@
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 0.10.1;
MARKETING_VERSION = 0.11.0;
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS";
Expand Down Expand Up @@ -980,7 +996,7 @@
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 0.10.1;
MARKETING_VERSION = 0.11.0;
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS";
Expand Down Expand Up @@ -1093,15 +1109,15 @@
repositoryURL = "https://github.com/pubnub/kmp-chat/";
requirement = {
kind = exactVersion;
version = "0.10.0-dev";
version = "0.11.0-dev";
};
};
3DCF7DFA2CD0FFCC00889326 /* XCRemoteSwiftPackageReference "swift" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/pubnub/swift";
requirement = {
kind = exactVersion;
version = 8.2.3;
version = 8.3.0;
};
};
/* End XCRemoteSwiftPackageReference section */
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ You will need the publish and subscribe keys to authenticate your app. Get your
1. Create or open your project inside Xcode.
2. Navigate to **File -> Add Package Dependencies**.
3. Search for `https://github.com/pubnub/swift-chat-sdk`
4. From the **Dependency Rule** drop-down list, select **Exact**. In the version input field, type `0.10.1-dev`
4. From the **Dependency Rule** drop-down list, select **Exact**. In the version input field, type `0.11.0-dev`
5. Click the **Add Package** button.

For more information see Apple's guide on [Adding Package Dependencies to Your App](https://developer.apple.com/documentation/xcode/adding_package_dependencies_to_your_app)
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 ``MutedUsersManagerInterface`` 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 @@ -44,6 +41,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 @@ -54,6 +52,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
2 changes: 1 addition & 1 deletion Sources/Entities/Membership+AsyncAwait.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public extension Membership {
/// Returns the number of messages you didn't read on a given channel. You can display this number on UI in the channel list of your chat app.
///
/// - Returns: A number of unread messages
func getUnreadMessagesCount() async throws -> UInt64 {
func getUnreadMessagesCount() async throws -> UInt64? {
try await withCheckedThrowingContinuation { continuation in
getUnreadMessagesCount {
switch $0 {
Expand Down
4 changes: 2 additions & 2 deletions Sources/Entities/Membership.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ public protocol Membership {
///
/// - Parameters:
/// - completion: The async `Result` of the method call
/// - **Success**: The number of unread messages on the membership's channel
/// - **Success**: The number of unread messages on the membership's channel or `nil` when ``lastReadMessageTimetoken`` is also `nil`
/// - **Failure**: An `Error` describing the failure
func getUnreadMessagesCount(
completion: ((Swift.Result<UInt64, Error>) -> Void)?
completion: ((Swift.Result<UInt64?, Error>) -> Void)?
)

/// You can receive updates when specific user-channel Membership object(s) are added, edited, or removed.
Expand Down
4 changes: 2 additions & 2 deletions Sources/Entities/MembershipImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ extension MembershipImpl: Membership {
}
}

public func getUnreadMessagesCount(completion: ((Swift.Result<UInt64, Error>) -> Void)? = nil) {
membership.getUnreadMessagesCount().async(caller: self) { (result: FutureResult<MembershipImpl, UInt64>) in
public func getUnreadMessagesCount(completion: ((Swift.Result<UInt64?, Error>) -> Void)? = nil) {
membership.getUnreadMessagesCount().async(caller: self) { (result: FutureResult<MembershipImpl, UInt64?>) in
switch result.result {
case let .success(messagesCount):
completion?(.success(messagesCount))
Expand Down
2 changes: 1 addition & 1 deletion Sources/Entities/Message.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ public protocol Message {
var actions: [String: [String: [Action]]]? { get }
/// Extra information added to the message giving additional context
var meta: [String: JSONCodable]? { get }
/// List of mentioned users with IDs and names

/// List of mentioned users with IDs and names
@available(*, deprecated, message: "Use `Message.getMessageElements()` instead")
var mentionedUsers: MessageMentionedUsers? { get }
/// List of referenced channels with IDs and names
Expand Down
25 changes: 25 additions & 0 deletions Sources/Entities/User+AsyncAwait.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,31 @@ public extension User {
}
}

/// 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.
///
/// - Parameter updateAction: A function for computing new values for the `User` fields based on the provided `User` argument and returning changes to apply
/// - Returns: The updated user object with its metadata
func update(
updateAction: @escaping (ChatType.ChatUserType) -> [PubNubMetadataChange<PubNubUserMetadata>]
) async throws -> ChatType.ChatUserType {
try await withCheckedThrowingContinuation { continuation in
update(updateAction: updateAction) {
switch $0 {
case let .success(user):
continuation.resume(returning: user)
case let .failure(error):
continuation.resume(throwing: error)
}
}
}
}

/// Deletes the user. If soft deletion is enabled, the user's data is retained but marked as inactive.
///
/// - Parameter soft: If true, the user is soft deleted, retaining their data but making them inactive
Expand Down
Loading

0 comments on commit 21c236a

Please sign in to comment.