From e379ea0f6650910afee3c292e8d597db16d7b3d0 Mon Sep 17 00:00:00 2001
From: Nuno Vieira
Date: Wed, 5 Nov 2025 21:04:37 +0000
Subject: [PATCH 01/10] Fix duplicated symbols for
MessageDeliveryCriteriaValidator_Mock (#3871)
---
...essageDeliveryCriteriaValidator_Mock.swift | 36 -------------------
1 file changed, 36 deletions(-)
delete mode 100644 TestTools/StreamChatTestTools/Mocks/StreamChat/Models/MessageDeliveryCriteriaValidator_Mock.swift
diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Models/MessageDeliveryCriteriaValidator_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Models/MessageDeliveryCriteriaValidator_Mock.swift
deleted file mode 100644
index cb6447923b..0000000000
--- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Models/MessageDeliveryCriteriaValidator_Mock.swift
+++ /dev/null
@@ -1,36 +0,0 @@
-//
-// Copyright © 2025 Stream.io Inc. All rights reserved.
-//
-
-import Foundation
-@testable import StreamChat
-
-/// A mock implementation of `MessageDeliveryCriteriaValidating` for testing purposes.
-final class MessageDeliveryCriteriaValidator_Mock: MessageDeliveryCriteriaValidating {
- var canMarkMessageAsDeliveredClosure: ((ChatMessage, CurrentChatUser, ChatChannel) -> Bool)?
- var canMarkMessageAsDeliveredCallCount = 0
- var canMarkMessageAsDeliveredCalledWithMessage: ChatMessage?
- var canMarkMessageAsDeliveredCalledWithCurrentUser: CurrentChatUser?
- var canMarkMessageAsDeliveredCalledWithChannel: ChatChannel?
-
- func canMarkMessageAsDelivered(
- _ message: ChatMessage,
- for currentUser: CurrentChatUser,
- in channel: ChatChannel
- ) -> Bool {
- canMarkMessageAsDeliveredCallCount += 1
- canMarkMessageAsDeliveredCalledWithMessage = message
- canMarkMessageAsDeliveredCalledWithCurrentUser = currentUser
- canMarkMessageAsDeliveredCalledWithChannel = channel
-
- return canMarkMessageAsDeliveredClosure?(message, currentUser, channel) ?? false
- }
-
- func reset() {
- canMarkMessageAsDeliveredClosure = nil
- canMarkMessageAsDeliveredCallCount = 0
- canMarkMessageAsDeliveredCalledWithMessage = nil
- canMarkMessageAsDeliveredCalledWithCurrentUser = nil
- canMarkMessageAsDeliveredCalledWithChannel = nil
- }
-}
From ca321e717026b3dc8ce935eade28ec30c1d140c0 Mon Sep 17 00:00:00 2001
From: Nuno Vieira
Date: Wed, 5 Nov 2025 22:58:56 +0000
Subject: [PATCH 02/10] Update CHANGELOG.md
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3140c7de0a..864ae6e7bd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## StreamChat
### ✅ Added
- Add support for message delivered info [#3846](https://github.com/GetStream/stream-chat-swift/pull/3846)
- - Add `ChatRemoteNotificationHandler.markMessageAsDelivered(deliveries:)`
+ - Add `ChatRemoteNotificationHandler.markMessageAsDelivered(message:channel:)`
- Add `ChatChannel.reads(message:)` and `ChatChannel.deliveredReads(message:)`
- Add `ChatChannelRead.lastDeliveredAt`
- Add `ChatChannelRead.lastDeliveredMessageId`
From bb94828f40fde2ee779bd58baa0e19a247997276 Mon Sep 17 00:00:00 2001
From: Nuno Vieira
Date: Fri, 7 Nov 2025 12:37:42 +0000
Subject: [PATCH 03/10] Add an example to the demo app to upload a global image
to change the user avatar (#3870)
---
.../UserProfileViewController.swift | 142 +++++++++++++++++-
1 file changed, 140 insertions(+), 2 deletions(-)
diff --git a/DemoApp/Screens/UserProfile/UserProfileViewController.swift b/DemoApp/Screens/UserProfile/UserProfileViewController.swift
index 73bd527203..c8d1e8d764 100644
--- a/DemoApp/Screens/UserProfile/UserProfileViewController.swift
+++ b/DemoApp/Screens/UserProfile/UserProfileViewController.swift
@@ -6,9 +6,10 @@ import StreamChat
import SwiftUI
import UIKit
-class UserProfileViewController: UITableViewController, CurrentChatUserControllerDelegate {
+class UserProfileViewController: UITableViewController, CurrentChatUserControllerDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
private let imageView = UIImageView()
private let updateButton = UIButton()
+ private let loadingSpinner = UIActivityIndicatorView(style: .medium)
var name: String?
let properties = UserProperty.allCases
@@ -42,28 +43,39 @@ class UserProfileViewController: UITableViewController, CurrentChatUserControlle
tableView.allowsSelection = false
view.backgroundColor = .systemBackground
- [imageView, updateButton].forEach {
+ [imageView, updateButton, loadingSpinner].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
tableView.tableHeaderView = UIView(frame: .init(origin: .zero, size: .init(width: .zero, height: 80)))
tableView.tableHeaderView?.addSubview(imageView)
+ tableView.tableHeaderView?.addSubview(loadingSpinner)
tableView.tableFooterView = UIView(frame: .init(origin: .zero, size: .init(width: .zero, height: 80)))
tableView.tableFooterView?.addSubview(updateButton)
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = 30
imageView.layer.masksToBounds = true
+ imageView.isUserInteractionEnabled = true
+
+ let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapImageView))
+ imageView.addGestureRecognizer(tapGesture)
+
updateButton.setTitle("Update", for: .normal)
updateButton.layer.cornerRadius = 4
updateButton.backgroundColor = .systemBlue
updateButton.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 15, bottom: 0.0, right: 15)
updateButton.addTarget(self, action: #selector(didTapUpdateButton), for: .touchUpInside)
+
+ loadingSpinner.hidesWhenStopped = true
+ loadingSpinner.color = .systemGray
NSLayoutConstraint.activate([
imageView.widthAnchor.constraint(equalToConstant: 60),
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor),
imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
+ loadingSpinner.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
+ loadingSpinner.centerYAnchor.constraint(equalTo: imageView.centerYAnchor),
updateButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
updateButton.heightAnchor.constraint(equalToConstant: 35),
updateButton.centerYAnchor.constraint(equalTo: updateButton.superview!.centerYAnchor)
@@ -240,4 +252,130 @@ class UserProfileViewController: UITableViewController, CurrentChatUserControlle
label.sizeToFit()
return label
}
+
+ // MARK: - Avatar Change
+
+ @objc private func didTapImageView() {
+ let alertController = UIAlertController(title: "Change Avatar", message: nil, preferredStyle: .actionSheet)
+
+ if UIImagePickerController.isSourceTypeAvailable(.camera) {
+ alertController.addAction(UIAlertAction(title: "Take Photo", style: .default) { [weak self] _ in
+ self?.presentImagePicker(sourceType: .camera)
+ })
+ }
+
+ alertController.addAction(UIAlertAction(title: "Choose from Library", style: .default) { [weak self] _ in
+ self?.presentImagePicker(sourceType: .photoLibrary)
+ })
+
+ alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel))
+
+ if let popover = alertController.popoverPresentationController {
+ popover.sourceView = imageView
+ popover.sourceRect = imageView.bounds
+ }
+
+ present(alertController, animated: true)
+ }
+
+ private func presentImagePicker(sourceType: UIImagePickerController.SourceType) {
+ let picker = UIImagePickerController()
+ picker.sourceType = sourceType
+ picker.delegate = self
+ picker.allowsEditing = true
+ present(picker, animated: true)
+ }
+
+ // MARK: - UIImagePickerControllerDelegate
+
+ func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
+ picker.dismiss(animated: true)
+
+ guard let selectedImage = (info[.editedImage] as? UIImage) ?? (info[.originalImage] as? UIImage) else {
+ return
+ }
+
+ loadingSpinner.startAnimating()
+
+ uploadImageAndUpdateProfile(selectedImage) { [weak self] error in
+ self?.loadingSpinner.stopAnimating()
+ if let error = error {
+ self?.showError(error)
+ } else {
+ self?.showSuccess()
+ }
+ }
+ }
+
+ func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
+ picker.dismiss(animated: true)
+ }
+
+ private func uploadImageAndUpdateProfile(_ image: UIImage, completion: @escaping (Error?) -> Void) {
+ guard let imageData = image.pngData() else {
+ completion(NSError(domain: "UserProfile", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to convert image to PNG data"]))
+ return
+ }
+
+ // Create temporary file
+ let imageURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("avatar_\(UUID().uuidString).png")
+
+ do {
+ try imageData.write(to: imageURL)
+ } catch {
+ completion(error)
+ return
+ }
+
+ let uploadingState = AttachmentUploadingState(
+ localFileURL: imageURL,
+ state: .pendingUpload,
+ file: .init(type: .png, size: Int64(imageData.count), mimeType: "image/png")
+ )
+
+ let attachment = StreamAttachment(
+ type: .image,
+ payload: imageData,
+ downloadingState: nil,
+ uploadingState: uploadingState
+ )
+
+ // Upload the image
+ currentUserController.client.upload(attachment, progress: { progress in
+ print("Upload progress: \(progress)")
+ }, completion: { [weak self] result in
+ // Clean up temporary file
+ try? FileManager.default.removeItem(at: imageURL)
+
+ switch result {
+ case .success(let file):
+ // Update user profile with new image URL
+ self?.currentUserController.updateUserData(imageURL: file.fileURL) { error in
+ completion(error)
+ }
+ case .failure(let error):
+ completion(error)
+ }
+ })
+ }
+
+ private func showError(_ error: Error) {
+ let alert = UIAlertController(
+ title: "Upload Failed",
+ message: error.localizedDescription,
+ preferredStyle: .alert
+ )
+ alert.addAction(UIAlertAction(title: "OK", style: .default))
+ present(alert, animated: true)
+ }
+
+ private func showSuccess() {
+ let alert = UIAlertController(
+ title: "Success",
+ message: "Avatar updated successfully!",
+ preferredStyle: .alert
+ )
+ alert.addAction(UIAlertAction(title: "OK", style: .default))
+ present(alert, animated: true)
+ }
}
From 1594c1a542022eab47ddec2d0b37924193ec14ea Mon Sep 17 00:00:00 2001
From: Stream Bot
Date: Fri, 7 Nov 2025 13:33:16 +0000
Subject: [PATCH 04/10] Update release version to snapshot
---
Sources/StreamChat/Generated/SystemEnvironment+Version.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Sources/StreamChat/Generated/SystemEnvironment+Version.swift b/Sources/StreamChat/Generated/SystemEnvironment+Version.swift
index 5db32bbee3..fc8290c558 100644
--- a/Sources/StreamChat/Generated/SystemEnvironment+Version.swift
+++ b/Sources/StreamChat/Generated/SystemEnvironment+Version.swift
@@ -7,5 +7,5 @@ import Foundation
extension SystemEnvironment {
/// A Stream Chat version.
- public static let version: String = "4.92.0"
+ public static let version: String = "4.93.0-SNAPSHOT"
}
From e6c940e3209570998ec1c934da9013dff7b8f72d Mon Sep 17 00:00:00 2001
From: Alexey Alter-Pesotskiy
Date: Mon, 10 Nov 2025 12:21:36 +0000
Subject: [PATCH 05/10] [CI] Bump xcsize plugin version (#3872)
---
Gemfile.lock | 8 ++++----
fastlane/Pluginfile | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index 8d78597140..83f8a70577 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -212,8 +212,8 @@ GEM
fastlane-plugin-stream_actions (0.3.101)
xctest_list (= 1.2.1)
fastlane-plugin-versioning (0.7.1)
- fastlane-plugin-xcsize (1.1.0)
- xcsize (= 1.1.0)
+ fastlane-plugin-xcsize (1.2.0)
+ xcsize (= 1.2.0)
fastlane-sirp (1.0.0)
sysrandom (~> 1.0)
faye-websocket (0.12.0)
@@ -433,7 +433,7 @@ GEM
rouge (~> 3.28.0)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
- xcsize (1.1.0)
+ xcsize (1.2.0)
commander (>= 4.6, < 6.0)
xctest_list (1.2.1)
@@ -451,7 +451,7 @@ DEPENDENCIES
fastlane-plugin-sonarcloud_metric_kit
fastlane-plugin-stream_actions (= 0.3.101)
fastlane-plugin-versioning
- fastlane-plugin-xcsize (= 1.1.0)
+ fastlane-plugin-xcsize (= 1.2.0)
faye-websocket
json
lefthook
diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile
index 40535271b0..9f8b4d3d92 100644
--- a/fastlane/Pluginfile
+++ b/fastlane/Pluginfile
@@ -6,4 +6,4 @@ gem 'fastlane-plugin-versioning'
gem 'fastlane-plugin-create_xcframework'
gem 'fastlane-plugin-sonarcloud_metric_kit'
gem 'fastlane-plugin-stream_actions', '0.3.101'
-gem 'fastlane-plugin-xcsize', '1.1.0'
+gem 'fastlane-plugin-xcsize', '1.2.0'
From 1f9b6c3dc9587de1ae9929d13ed6b22542873cf0 Mon Sep 17 00:00:00 2001
From: Nuno Vieira
Date: Thu, 13 Nov 2025 16:23:29 +0000
Subject: [PATCH 06/10] Remove timestamp from demo app message reads info view
(#3877)
---
.../DemoMessageReadsInfoView.swift | 29 ++-----------------
1 file changed, 2 insertions(+), 27 deletions(-)
diff --git a/DemoApp/StreamChat/Components/DeliveredMessages/DemoMessageReadsInfoView.swift b/DemoApp/StreamChat/Components/DeliveredMessages/DemoMessageReadsInfoView.swift
index bf6386d905..d42fc402a4 100644
--- a/DemoApp/StreamChat/Components/DeliveredMessages/DemoMessageReadsInfoView.swift
+++ b/DemoApp/StreamChat/Components/DeliveredMessages/DemoMessageReadsInfoView.swift
@@ -29,8 +29,7 @@ struct DemoMessageReadsInfoView: View {
ForEach(deliveredUsers, id: \.id) { user in
UserReadInfoRow(
user: user,
- status: .delivered,
- timestamp: getDeliveredTimestamp(for: user)
+ status: .delivered
)
}
}
@@ -41,8 +40,7 @@ struct DemoMessageReadsInfoView: View {
ForEach(readUsers, id: \.id) { user in
UserReadInfoRow(
user: user,
- status: .read,
- timestamp: getReadTimestamp(for: user)
+ status: .read
)
}
}
@@ -135,7 +133,6 @@ struct DemoMessageReadsInfoView: View {
struct UserReadInfoRow: View {
let user: ChatUser
let status: ReadStatus
- let timestamp: Date?
enum ReadStatus {
case delivered
@@ -184,12 +181,6 @@ struct UserReadInfoRow: View {
Text(user.name ?? user.id)
.font(.headline)
.foregroundColor(.primary)
-
- if let timestamp = timestamp {
- Text(formatTimestamp(timestamp))
- .font(.caption)
- .foregroundColor(.secondary)
- }
}
Spacer()
@@ -201,20 +192,4 @@ struct UserReadInfoRow: View {
}
.padding(.vertical, 4)
}
-
- private func formatTimestamp(_ date: Date) -> String {
- let formatter = DateFormatter()
- formatter.dateStyle = .none
- formatter.timeStyle = .short
-
- let calendar = Calendar.current
- if calendar.isDateInToday(date) {
- return "Today at \(formatter.string(from: date))"
- } else if calendar.isDateInYesterday(date) {
- return "Yesterday at \(formatter.string(from: date))"
- } else {
- formatter.dateStyle = .short
- return formatter.string(from: date)
- }
- }
}
From 32ddf8100a318ab860c7d0f56276c787d5fce733 Mon Sep 17 00:00:00 2001
From: Alexey Alter-Pesotskiy
Date: Fri, 14 Nov 2025 12:08:01 +0000
Subject: [PATCH 07/10] [CI] Introduce Backend Integration Tests (#3876)
---
.github/workflows/backend-checks.yml | 41 ++++++++++++
StreamChat.xcodeproj/project.pbxproj | 14 ++++
.../xcschemes/StreamChatUITestsApp.xcscheme | 3 +
.../Backend.xctestplan | 28 ++++++++
.../StreamChatUITestsApp.xctestplan | 1 +
.../Tests/Backend/Backend_Tests.swift | 67 +++++++++++++++++++
fastlane/Fastfile | 26 +++++++
7 files changed, 180 insertions(+)
create mode 100644 .github/workflows/backend-checks.yml
create mode 100644 StreamChatUITestsAppUITests/Backend.xctestplan
create mode 100644 StreamChatUITestsAppUITests/Tests/Backend/Backend_Tests.swift
diff --git a/.github/workflows/backend-checks.yml b/.github/workflows/backend-checks.yml
new file mode 100644
index 0000000000..71be1ae63d
--- /dev/null
+++ b/.github/workflows/backend-checks.yml
@@ -0,0 +1,41 @@
+name: Backend Checks
+
+on:
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+env:
+ HOMEBREW_NO_INSTALL_CLEANUP: 1 # Disable cleanup for homebrew, we don't need it on CI
+ IOS_SIMULATOR_DEVICE: "iPhone 16 Pro (18.5)"
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+jobs:
+ test-backend-integration:
+ name: Test Backend Integration
+ runs-on: macos-15
+ steps:
+ - uses: actions/checkout@v4.1.1
+ - uses: ./.github/actions/bootstrap
+ env:
+ INSTALL_YEETD: true
+ SKIP_SWIFT_BOOTSTRAP: true
+ - name: Run UI Tests (Debug)
+ run: bundle exec fastlane test_e2e device:"${{ env.IOS_SIMULATOR_DEVICE }}"
+ timeout-minutes: 100
+ - name: Parse xcresult
+ if: failure()
+ run: |
+ brew install chargepoint/xcparse/xcparse
+ xcparse logs fastlane/test_output/StreamChatUITestsApp.xcresult fastlane/test_output/logs/
+ - uses: actions/upload-artifact@v4
+ if: failure()
+ with:
+ name: Test Data Backend Integration
+ path: |
+ fastlane/recordings
+ fastlane/sinatra_log.txt
+ fastlane/test_output/logs/*/Diagnostics/**/*.txt
+ fastlane/test_output/logs/*/Diagnostics/simctl_diagnostics/DiagnosticReports/*
diff --git a/StreamChat.xcodeproj/project.pbxproj b/StreamChat.xcodeproj/project.pbxproj
index 5c5e2b13c0..91f358ec03 100644
--- a/StreamChat.xcodeproj/project.pbxproj
+++ b/StreamChat.xcodeproj/project.pbxproj
@@ -629,6 +629,7 @@
8274A7962B7FAC3900D8696B /* ChannelListScrollTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8274A7952B7FAC3900D8696B /* ChannelListScrollTime.swift */; };
8279706F29689680006741A3 /* UserDetails_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8279706E29689680006741A3 /* UserDetails_Tests.swift */; };
827DD1A0289D5B3300910AC5 /* MessageActionsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 827DD19F289D5B3300910AC5 /* MessageActionsVC.swift */; };
+ 82865DA42EC4B87B007D7053 /* Backend_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82865DA32EC4B874007D7053 /* Backend_Tests.swift */; };
8292D6DB29B78476007A17D1 /* QuotedReply_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8292D6DA29B78476007A17D1 /* QuotedReply_Tests.swift */; };
829762E028C7587500B953E8 /* PushNotification_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 829762DF28C7587500B953E8 /* PushNotification_Tests.swift */; };
829CD5C52848C2EA003C3877 /* ParticipantRobot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 829CD5C32848C25F003C3877 /* ParticipantRobot.swift */; };
@@ -3691,6 +3692,8 @@
8274A7952B7FAC3900D8696B /* ChannelListScrollTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelListScrollTime.swift; sourceTree = ""; };
8279706E29689680006741A3 /* UserDetails_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDetails_Tests.swift; sourceTree = ""; };
827DD19F289D5B3300910AC5 /* MessageActionsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActionsVC.swift; sourceTree = ""; };
+ 82865DA12EC4B84F007D7053 /* Backend.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = Backend.xctestplan; path = StreamChatUITestsAppUITests/Backend.xctestplan; sourceTree = ""; };
+ 82865DA32EC4B874007D7053 /* Backend_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backend_Tests.swift; sourceTree = ""; };
8292D6DA29B78476007A17D1 /* QuotedReply_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuotedReply_Tests.swift; sourceTree = ""; };
829762DF28C7587500B953E8 /* PushNotification_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotification_Tests.swift; sourceTree = ""; };
8298C8E827D22C3E004082D3 /* UserRobot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRobot.swift; sourceTree = ""; };
@@ -6312,6 +6315,14 @@
path = Swifter;
sourceTree = "";
};
+ 82865DA22EC4B86A007D7053 /* Backend */ = {
+ isa = PBXGroup;
+ children = (
+ 82865DA32EC4B874007D7053 /* Backend_Tests.swift */,
+ );
+ path = Backend;
+ sourceTree = "";
+ };
829CD5C22848C244003C3877 /* Robots */ = {
isa = PBXGroup;
children = (
@@ -6370,6 +6381,7 @@
82AD02BE27D8E453000611B7 /* Tests */ = {
isa = PBXGroup;
children = (
+ 82865DA22EC4B86A007D7053 /* Backend */,
82EBA1822B30A63800B3A048 /* Performance */,
A3600B3D283F63C700E1C930 /* Base TestCase */,
A3B78F16282A670600348AD1 /* Message Delivery Status */,
@@ -6843,6 +6855,7 @@
8AD5EC8522E9A3E8005CFAC9 = {
isa = PBXGroup;
children = (
+ 82865DA12EC4B84F007D7053 /* Backend.xctestplan */,
4A4E184528D06CA30062378D /* Documentation.docc */,
AD9BE32526680E4200A6D284 /* Stream.playground */,
792E3D6A25C97D920040B0C2 /* Package.swift */,
@@ -12534,6 +12547,7 @@
8232B84F28635C4A0032C7DB /* Attachments_Tests.swift in Sources */,
822F266027D9FDB500E454FB /* URLProtocol_Mock.swift in Sources */,
82BA52EF27E1EF7B00951B87 /* MessageList_Tests.swift in Sources */,
+ 82865DA42EC4B87B007D7053 /* Backend_Tests.swift in Sources */,
A33FA818282E559A00DC40E8 /* SlowMode_Tests.swift in Sources */,
A39B040B27F196F200D6B18A /* StreamChatUITests.swift in Sources */,
825A32CF27DBB48D000402A9 /* StartPage.swift in Sources */,
diff --git a/StreamChat.xcodeproj/xcshareddata/xcschemes/StreamChatUITestsApp.xcscheme b/StreamChat.xcodeproj/xcshareddata/xcschemes/StreamChatUITestsApp.xcscheme
index 471c2cbed6..46dea19be2 100644
--- a/StreamChat.xcodeproj/xcshareddata/xcschemes/StreamChatUITestsApp.xcscheme
+++ b/StreamChat.xcodeproj/xcshareddata/xcschemes/StreamChatUITestsApp.xcscheme
@@ -35,6 +35,9 @@
+
+
Date: Mon, 17 Nov 2025 16:16:50 +0000
Subject: [PATCH 08/10] Reduce SDK size by converting Events from structs to
classes (#3878)
* Reduce SDK size by converting Events from struct to classes
* Update CHANGELOG.md
* Make all events final
* Change missing events to classes
---
CHANGELOG.md | 4 +
.../Events/AITypingEvents.swift | 25 +++-
.../Events/ChannelEvents.swift | 43 ++++++-
.../Events/ConnectionEvents.swift | 2 +-
.../WebSocketClient/Events/DraftEvents.swift | 4 +-
.../WebSocketClient/Events/MemberEvents.swift | 26 ++++-
.../Events/MessageEvents.swift | 14 +--
.../Events/NotificationEvents.swift | 107 ++++++++++++++++--
.../WebSocketClient/Events/PollsEvents.swift | 52 +++++++--
.../Events/ReactionEvents.swift | 30 ++++-
.../Events/ReminderEvents.swift | 8 +-
.../WebSocketClient/Events/ThreadEvents.swift | 16 ++-
.../WebSocketClient/Events/TypingEvent.swift | 12 +-
.../WebSocketClient/Events/UserEvents.swift | 70 ++++++++++--
14 files changed, 352 insertions(+), 61 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 58a9ec1a05..caa7095bc6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,7 +3,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
# Upcoming
+## StreamChat
+### ⚡️ Performance
+- Reduce SDK size by converting Events from structs to classes [#3878](https://github.com/GetStream/stream-chat-swift/pull/3878)
### 🔄 Changed
+- Change Events from structs to classes [#3878](https://github.com/GetStream/stream-chat-swift/pull/3878)
# [4.92.0](https://github.com/GetStream/stream-chat-swift/releases/tag/4.92.0)
_November 05, 2025_
diff --git a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift
index 7db59d3e52..c66bd3737a 100644
--- a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift
+++ b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift
@@ -5,7 +5,7 @@
import Foundation
/// An event that provides updates about the state of the AI typing indicator.
-public struct AIIndicatorUpdateEvent: Event {
+public final class AIIndicatorUpdateEvent: Event {
/// The state of the AI typing indicator.
public let state: AITypingState
/// The channel ID this event is related to.
@@ -14,6 +14,13 @@ public struct AIIndicatorUpdateEvent: Event {
public let messageId: MessageId?
/// Optional server message, usually when an error occurs.
public let aiMessage: String?
+
+ init(state: AITypingState, cid: ChannelId?, messageId: MessageId?, aiMessage: String?) {
+ self.state = state
+ self.cid = cid
+ self.messageId = messageId
+ self.aiMessage = aiMessage
+ }
}
class AIIndicatorUpdateEventDTO: EventDTO {
@@ -39,9 +46,13 @@ class AIIndicatorUpdateEventDTO: EventDTO {
}
/// An event that clears the AI typing indicator.
-public struct AIIndicatorClearEvent: Event {
+public final class AIIndicatorClearEvent: Event {
/// The channel ID this event is related to.
public let cid: ChannelId?
+
+ init(cid: ChannelId?) {
+ self.cid = cid
+ }
}
class AIIndicatorClearEventDTO: EventDTO {
@@ -57,7 +68,7 @@ class AIIndicatorClearEventDTO: EventDTO {
}
/// An event that indicates the AI has stopped generating the message.
-public struct AIIndicatorStopEvent: CustomEventPayload, Event {
+public final class AIIndicatorStopEvent: CustomEventPayload, Event {
public static var eventType: EventType = .aiTypingIndicatorStop
/// The channel ID this event is related to.
@@ -66,6 +77,14 @@ public struct AIIndicatorStopEvent: CustomEventPayload, Event {
public init(cid: ChannelId?) {
self.cid = cid
}
+
+ public func hash(into hasher: inout Hasher) {
+ hasher.combine(cid)
+ }
+
+ public static func == (lhs: AIIndicatorStopEvent, rhs: AIIndicatorStopEvent) -> Bool {
+ lhs.cid == rhs.cid
+ }
}
class AIIndicatorStopEventDTO: EventDTO {
diff --git a/Sources/StreamChat/WebSocketClient/Events/ChannelEvents.swift b/Sources/StreamChat/WebSocketClient/Events/ChannelEvents.swift
index bfc09d5007..8bbf8f997c 100644
--- a/Sources/StreamChat/WebSocketClient/Events/ChannelEvents.swift
+++ b/Sources/StreamChat/WebSocketClient/Events/ChannelEvents.swift
@@ -5,7 +5,7 @@
import Foundation
/// Triggered when a channel is updated.
-public struct ChannelUpdatedEvent: ChannelSpecificEvent {
+public final class ChannelUpdatedEvent: ChannelSpecificEvent {
/// The identifier of updated channel.
public var cid: ChannelId { channel.cid }
@@ -20,6 +20,13 @@ public struct ChannelUpdatedEvent: ChannelSpecificEvent {
/// The event timestamp.
public let createdAt: Date
+
+ init(channel: ChatChannel, user: ChatUser?, message: ChatMessage?, createdAt: Date) {
+ self.channel = channel
+ self.user = user
+ self.message = message
+ self.createdAt = createdAt
+ }
}
class ChannelUpdatedEventDTO: EventDTO {
@@ -53,7 +60,7 @@ class ChannelUpdatedEventDTO: EventDTO {
}
/// Triggered when a channel is deleted.
-public struct ChannelDeletedEvent: ChannelSpecificEvent {
+public final class ChannelDeletedEvent: ChannelSpecificEvent {
/// The identifier of deleted channel.
public var cid: ChannelId { channel.cid }
@@ -65,6 +72,12 @@ public struct ChannelDeletedEvent: ChannelSpecificEvent {
/// The event timestamp.
public let createdAt: Date
+
+ init(channel: ChatChannel, user: ChatUser?, createdAt: Date) {
+ self.channel = channel
+ self.user = user
+ self.createdAt = createdAt
+ }
}
class ChannelDeletedEventDTO: EventDTO {
@@ -94,7 +107,7 @@ class ChannelDeletedEventDTO: EventDTO {
}
/// Triggered when a channel is truncated.
-public struct ChannelTruncatedEvent: ChannelSpecificEvent {
+public final class ChannelTruncatedEvent: ChannelSpecificEvent {
/// The identifier of deleted channel.
public var cid: ChannelId { channel.cid }
@@ -109,6 +122,13 @@ public struct ChannelTruncatedEvent: ChannelSpecificEvent {
/// The event timestamp.
public let createdAt: Date
+
+ init(channel: ChatChannel, user: ChatUser?, message: ChatMessage?, createdAt: Date) {
+ self.channel = channel
+ self.user = user
+ self.message = message
+ self.createdAt = createdAt
+ }
}
class ChannelTruncatedEventDTO: EventDTO {
@@ -142,7 +162,7 @@ class ChannelTruncatedEventDTO: EventDTO {
}
/// Triggered when a channel is made visible.
-public struct ChannelVisibleEvent: ChannelSpecificEvent {
+public final class ChannelVisibleEvent: ChannelSpecificEvent {
/// The channel identifier.
public let cid: ChannelId
@@ -151,6 +171,12 @@ public struct ChannelVisibleEvent: ChannelSpecificEvent {
/// The event timestamp.
public let createdAt: Date
+
+ init(cid: ChannelId, user: ChatUser, createdAt: Date) {
+ self.cid = cid
+ self.user = user
+ self.createdAt = createdAt
+ }
}
class ChannelVisibleEventDTO: EventDTO {
@@ -178,7 +204,7 @@ class ChannelVisibleEventDTO: EventDTO {
}
/// Triggered when a channel is hidden.
-public struct ChannelHiddenEvent: ChannelSpecificEvent {
+public final class ChannelHiddenEvent: ChannelSpecificEvent {
/// The hidden channel identifier.
public let cid: ChannelId
@@ -190,6 +216,13 @@ public struct ChannelHiddenEvent: ChannelSpecificEvent {
/// The date a channel was hidden.
public let createdAt: Date
+
+ init(cid: ChannelId, user: ChatUser, isHistoryCleared: Bool, createdAt: Date) {
+ self.cid = cid
+ self.user = user
+ self.isHistoryCleared = isHistoryCleared
+ self.createdAt = createdAt
+ }
}
class ChannelHiddenEventDTO: EventDTO {
diff --git a/Sources/StreamChat/WebSocketClient/Events/ConnectionEvents.swift b/Sources/StreamChat/WebSocketClient/Events/ConnectionEvents.swift
index c8fcb3d998..0eaa70045c 100644
--- a/Sources/StreamChat/WebSocketClient/Events/ConnectionEvents.swift
+++ b/Sources/StreamChat/WebSocketClient/Events/ConnectionEvents.swift
@@ -36,7 +36,7 @@ public class HealthCheckEvent: ConnectionEvent, EventDTO {
/// Emitted when `Client` changes it's connection status. You can listen to this event and indicate the different connection
/// states in the UI (banners like "Offline", "Reconnecting"", etc.).
-public struct ConnectionStatusUpdated: Event {
+public final class ConnectionStatusUpdated: Event {
/// The current connection status of `Client`
public let connectionStatus: ConnectionStatus
diff --git a/Sources/StreamChat/WebSocketClient/Events/DraftEvents.swift b/Sources/StreamChat/WebSocketClient/Events/DraftEvents.swift
index 69263ba608..db10dec8f3 100644
--- a/Sources/StreamChat/WebSocketClient/Events/DraftEvents.swift
+++ b/Sources/StreamChat/WebSocketClient/Events/DraftEvents.swift
@@ -5,7 +5,7 @@
import Foundation
/// Triggered when a draft message is updated or created.
-public class DraftUpdatedEvent: Event {
+public final class DraftUpdatedEvent: Event {
/// The channel identifier of the draft.
public let cid: ChannelId
@@ -55,7 +55,7 @@ class DraftUpdatedEventDTO: EventDTO {
}
/// Triggered when a draft message is deleted.
-public class DraftDeletedEvent: Event {
+public final class DraftDeletedEvent: Event {
/// The channel identifier of the draft.
public let cid: ChannelId
diff --git a/Sources/StreamChat/WebSocketClient/Events/MemberEvents.swift b/Sources/StreamChat/WebSocketClient/Events/MemberEvents.swift
index efa4b30bff..f2e7d0726a 100644
--- a/Sources/StreamChat/WebSocketClient/Events/MemberEvents.swift
+++ b/Sources/StreamChat/WebSocketClient/Events/MemberEvents.swift
@@ -5,7 +5,7 @@
import Foundation
/// Triggered when a new member is added to a channel.
-public struct MemberAddedEvent: MemberEvent, ChannelSpecificEvent {
+public final class MemberAddedEvent: MemberEvent, ChannelSpecificEvent {
/// The user who added a member to a channel.
public let user: ChatUser
@@ -17,6 +17,13 @@ public struct MemberAddedEvent: MemberEvent, ChannelSpecificEvent {
/// The event timestamp.
public let createdAt: Date
+
+ init(user: ChatUser, cid: ChannelId, member: ChatChannelMember, createdAt: Date) {
+ self.user = user
+ self.cid = cid
+ self.member = member
+ self.createdAt = createdAt
+ }
}
class MemberAddedEventDTO: EventDTO {
@@ -50,7 +57,7 @@ class MemberAddedEventDTO: EventDTO {
}
/// Triggered when a channel member is updated.
-public struct MemberUpdatedEvent: MemberEvent, ChannelSpecificEvent {
+public final class MemberUpdatedEvent: MemberEvent, ChannelSpecificEvent {
/// The user who updated a member.
public let user: ChatUser
@@ -62,6 +69,13 @@ public struct MemberUpdatedEvent: MemberEvent, ChannelSpecificEvent {
/// The event timestamp.
public let createdAt: Date
+
+ init(user: ChatUser, cid: ChannelId, member: ChatChannelMember, createdAt: Date) {
+ self.user = user
+ self.cid = cid
+ self.member = member
+ self.createdAt = createdAt
+ }
}
class MemberUpdatedEventDTO: EventDTO {
@@ -95,7 +109,7 @@ class MemberUpdatedEventDTO: EventDTO {
}
/// Triggered when a member is removed from a channel.
-public struct MemberRemovedEvent: MemberEvent, ChannelSpecificEvent {
+public final class MemberRemovedEvent: MemberEvent, ChannelSpecificEvent {
/// The user who stopped being a member.
public let user: ChatUser
@@ -104,6 +118,12 @@ public struct MemberRemovedEvent: MemberEvent, ChannelSpecificEvent {
/// The event timestamp.
public let createdAt: Date
+
+ init(user: ChatUser, cid: ChannelId, createdAt: Date) {
+ self.user = user
+ self.cid = cid
+ self.createdAt = createdAt
+ }
}
class MemberRemovedEventDTO: EventDTO {
diff --git a/Sources/StreamChat/WebSocketClient/Events/MessageEvents.swift b/Sources/StreamChat/WebSocketClient/Events/MessageEvents.swift
index 4e7edbebf5..56e0002dbc 100644
--- a/Sources/StreamChat/WebSocketClient/Events/MessageEvents.swift
+++ b/Sources/StreamChat/WebSocketClient/Events/MessageEvents.swift
@@ -5,7 +5,7 @@
import Foundation
/// Triggered when a new message is sent to channel.
-public class MessageNewEvent: ChannelSpecificEvent, HasUnreadCount {
+public final class MessageNewEvent: ChannelSpecificEvent, HasUnreadCount {
/// The user who sent a message.
public let user: ChatUser
@@ -83,7 +83,7 @@ class MessageNewEventDTO: EventDTO {
}
/// Triggered when a message is updated.
-public class MessageUpdatedEvent: ChannelSpecificEvent {
+public final class MessageUpdatedEvent: ChannelSpecificEvent {
/// The use who updated the message.
public let user: ChatUser
@@ -144,7 +144,7 @@ class MessageUpdatedEventDTO: EventDTO {
}
/// Triggered when a new message is deleted.
-public class MessageDeletedEvent: ChannelSpecificEvent {
+public final class MessageDeletedEvent: ChannelSpecificEvent {
/// The user who deleted the message.
public let user: ChatUser?
@@ -233,7 +233,7 @@ class MessageDeletedEventDTO: EventDTO {
public typealias ChannelReadEvent = MessageReadEvent
/// `ChannelReadEvent`, this event tells that User has mark read all messages in channel.
-public class MessageReadEvent: ChannelSpecificEvent {
+public final class MessageReadEvent: ChannelSpecificEvent {
/// The user who read the channel.
public let user: ChatUser
@@ -305,7 +305,7 @@ class MessageReadEventDTO: EventDTO {
}
// Triggered when the current user creates a new message and is pending to be sent.
-public class NewMessagePendingEvent: ChannelSpecificEvent {
+public final class NewMessagePendingEvent: ChannelSpecificEvent {
public var message: ChatMessage
public var cid: ChannelId
@@ -316,7 +316,7 @@ public class NewMessagePendingEvent: ChannelSpecificEvent {
}
// Triggered when a message failed being sent.
-public class NewMessageErrorEvent: ChannelSpecificEvent {
+public final class NewMessageErrorEvent: ChannelSpecificEvent {
public let messageId: MessageId
public let cid: ChannelId
public let error: Error
@@ -329,7 +329,7 @@ public class NewMessageErrorEvent: ChannelSpecificEvent {
}
/// Triggered when a message is delivered to a user.
-public class MessageDeliveredEvent: ChannelSpecificEvent {
+public final class MessageDeliveredEvent: ChannelSpecificEvent {
/// The user who received the delivered message.
public let user: ChatUser
diff --git a/Sources/StreamChat/WebSocketClient/Events/NotificationEvents.swift b/Sources/StreamChat/WebSocketClient/Events/NotificationEvents.swift
index 27d1c7165b..37b4b24b71 100644
--- a/Sources/StreamChat/WebSocketClient/Events/NotificationEvents.swift
+++ b/Sources/StreamChat/WebSocketClient/Events/NotificationEvents.swift
@@ -5,7 +5,7 @@
import Foundation
/// Triggered when a new message is sent to a channel the current user is member of.
-public struct NotificationMessageNewEvent: ChannelSpecificEvent, HasUnreadCount {
+public final class NotificationMessageNewEvent: ChannelSpecificEvent, HasUnreadCount {
/// The identifier of a channel a message is sent to.
public var cid: ChannelId { channel.cid }
@@ -20,6 +20,13 @@ public struct NotificationMessageNewEvent: ChannelSpecificEvent, HasUnreadCount
/// The unread counts of the current user.
public let unreadCount: UnreadCount?
+
+ init(channel: ChatChannel, message: ChatMessage, createdAt: Date, unreadCount: UnreadCount?) {
+ self.channel = channel
+ self.message = message
+ self.createdAt = createdAt
+ self.unreadCount = unreadCount
+ }
}
class NotificationMessageNewEventDTO: EventDTO {
@@ -54,7 +61,7 @@ class NotificationMessageNewEventDTO: EventDTO {
}
/// Triggered when all channels the current user is member of are marked as read.
-public struct NotificationMarkAllReadEvent: Event, HasUnreadCount {
+public final class NotificationMarkAllReadEvent: Event, HasUnreadCount {
/// The current user.
public let user: ChatUser
@@ -63,6 +70,12 @@ public struct NotificationMarkAllReadEvent: Event, HasUnreadCount {
/// The event timestamp.
public let createdAt: Date
+
+ init(user: ChatUser, unreadCount: UnreadCount?, createdAt: Date) {
+ self.user = user
+ self.unreadCount = unreadCount
+ self.createdAt = createdAt
+ }
}
class NotificationMarkAllReadEventDTO: EventDTO {
@@ -91,7 +104,7 @@ class NotificationMarkAllReadEventDTO: EventDTO {
}
/// Triggered when a channel the current user is member of is marked as read.
-public struct NotificationMarkReadEvent: ChannelSpecificEvent, HasUnreadCount {
+public final class NotificationMarkReadEvent: ChannelSpecificEvent, HasUnreadCount {
/// The current user.
public let user: ChatUser
@@ -106,10 +119,18 @@ public struct NotificationMarkReadEvent: ChannelSpecificEvent, HasUnreadCount {
/// The event timestamp.
public let createdAt: Date
+
+ init(user: ChatUser, cid: ChannelId, unreadCount: UnreadCount?, lastReadMessageId: MessageId?, createdAt: Date) {
+ self.user = user
+ self.cid = cid
+ self.unreadCount = unreadCount
+ self.lastReadMessageId = lastReadMessageId
+ self.createdAt = createdAt
+ }
}
/// Triggered when a channel the current user is member of is marked as unread.
-public struct NotificationMarkUnreadEvent: ChannelSpecificEvent {
+public final class NotificationMarkUnreadEvent: ChannelSpecificEvent {
/// The current user.
public let user: ChatUser
@@ -133,6 +154,17 @@ public struct NotificationMarkUnreadEvent: ChannelSpecificEvent {
/// The number of unread messages for the channel
public let unreadMessagesCount: Int
+
+ init(user: ChatUser, cid: ChannelId, createdAt: Date, firstUnreadMessageId: MessageId, lastReadMessageId: MessageId?, lastReadAt: Date, unreadCount: UnreadCount, unreadMessagesCount: Int) {
+ self.user = user
+ self.cid = cid
+ self.createdAt = createdAt
+ self.firstUnreadMessageId = firstUnreadMessageId
+ self.lastReadMessageId = lastReadMessageId
+ self.lastReadAt = lastReadAt
+ self.unreadCount = unreadCount
+ self.unreadMessagesCount = unreadMessagesCount
+ }
}
class NotificationMarkReadEventDTO: EventDTO {
@@ -207,12 +239,17 @@ class NotificationMarkUnreadEventDTO: EventDTO {
}
/// Triggered when current user mutes/unmutes a user.
-public struct NotificationMutesUpdatedEvent: Event {
+public final class NotificationMutesUpdatedEvent: Event {
/// The current user.
public let currentUser: CurrentChatUser
/// The event timestamp.
public let createdAt: Date
+
+ init(currentUser: CurrentChatUser, createdAt: Date) {
+ self.currentUser = currentUser
+ self.createdAt = createdAt
+ }
}
class NotificationMutesUpdatedEventDTO: EventDTO {
@@ -237,7 +274,7 @@ class NotificationMutesUpdatedEventDTO: EventDTO {
}
/// Triggered when the current user is added to the channel member list.
-public struct NotificationAddedToChannelEvent: ChannelSpecificEvent, HasUnreadCount {
+public final class NotificationAddedToChannelEvent: ChannelSpecificEvent, HasUnreadCount {
/// The identifier of a channel a message is sent to.
public var cid: ChannelId { channel.cid }
@@ -252,6 +289,13 @@ public struct NotificationAddedToChannelEvent: ChannelSpecificEvent, HasUnreadCo
/// The event timestamp.
public let createdAt: Date
+
+ init(channel: ChatChannel, unreadCount: UnreadCount?, member: ChatChannelMember, createdAt: Date) {
+ self.channel = channel
+ self.unreadCount = unreadCount
+ self.member = member
+ self.createdAt = createdAt
+ }
}
class NotificationAddedToChannelEventDTO: EventDTO {
@@ -287,7 +331,7 @@ class NotificationAddedToChannelEventDTO: EventDTO {
}
/// Triggered when the current user is removed from a channel member list.
-public struct NotificationRemovedFromChannelEvent: ChannelSpecificEvent {
+public final class NotificationRemovedFromChannelEvent: ChannelSpecificEvent {
/// The user who removed the current user from channel members.
public let user: ChatUser
@@ -299,6 +343,13 @@ public struct NotificationRemovedFromChannelEvent: ChannelSpecificEvent {
/// The event timestamp.
public let createdAt: Date
+
+ init(user: ChatUser, cid: ChannelId, member: ChatChannelMember, createdAt: Date) {
+ self.user = user
+ self.cid = cid
+ self.member = member
+ self.createdAt = createdAt
+ }
}
class NotificationRemovedFromChannelEventDTO: EventDTO {
@@ -333,12 +384,17 @@ class NotificationRemovedFromChannelEventDTO: EventDTO {
}
/// Triggered when current user mutes/unmutes a channel.
-public struct NotificationChannelMutesUpdatedEvent: Event {
+public final class NotificationChannelMutesUpdatedEvent: Event {
/// The current user.
public let currentUser: CurrentChatUser
/// The event timestamp.
public let createdAt: Date
+
+ init(currentUser: CurrentChatUser, createdAt: Date) {
+ self.currentUser = currentUser
+ self.createdAt = createdAt
+ }
}
class NotificationChannelMutesUpdatedEventDTO: EventDTO {
@@ -363,7 +419,7 @@ class NotificationChannelMutesUpdatedEventDTO: EventDTO {
}
/// Triggered when current user is invited to a channel.
-public struct NotificationInvitedEvent: MemberEvent, ChannelSpecificEvent {
+public final class NotificationInvitedEvent: MemberEvent, ChannelSpecificEvent {
/// The inviter.
public let user: ChatUser
@@ -375,6 +431,13 @@ public struct NotificationInvitedEvent: MemberEvent, ChannelSpecificEvent {
/// The event timestamp.
public let createdAt: Date
+
+ init(user: ChatUser, cid: ChannelId, member: ChatChannelMember, createdAt: Date) {
+ self.user = user
+ self.cid = cid
+ self.member = member
+ self.createdAt = createdAt
+ }
}
class NotificationInvitedEventDTO: EventDTO {
@@ -409,7 +472,7 @@ class NotificationInvitedEventDTO: EventDTO {
}
/// Triggered when the current user accepts an invite to a channel.
-public struct NotificationInviteAcceptedEvent: MemberEvent, ChannelSpecificEvent {
+public final class NotificationInviteAcceptedEvent: MemberEvent, ChannelSpecificEvent {
/// The inviter.
public let user: ChatUser
@@ -424,6 +487,13 @@ public struct NotificationInviteAcceptedEvent: MemberEvent, ChannelSpecificEvent
/// The event timestamp.
public let createdAt: Date
+
+ init(user: ChatUser, channel: ChatChannel, member: ChatChannelMember, createdAt: Date) {
+ self.user = user
+ self.channel = channel
+ self.member = member
+ self.createdAt = createdAt
+ }
}
class NotificationInviteAcceptedEventDTO: EventDTO {
@@ -459,7 +529,7 @@ class NotificationInviteAcceptedEventDTO: EventDTO {
}
/// Triggered when the current user rejects an invite to a channel.
-public struct NotificationInviteRejectedEvent: MemberEvent, ChannelSpecificEvent {
+public final class NotificationInviteRejectedEvent: MemberEvent, ChannelSpecificEvent {
/// The inviter.
public let user: ChatUser
@@ -474,6 +544,13 @@ public struct NotificationInviteRejectedEvent: MemberEvent, ChannelSpecificEvent
/// The event timestamp.
public let createdAt: Date
+
+ init(user: ChatUser, channel: ChatChannel, member: ChatChannelMember, createdAt: Date) {
+ self.user = user
+ self.channel = channel
+ self.member = member
+ self.createdAt = createdAt
+ }
}
class NotificationInviteRejectedEventDTO: EventDTO {
@@ -509,7 +586,7 @@ class NotificationInviteRejectedEventDTO: EventDTO {
}
/// Triggered when a channel is deleted, this event is delivered to all channel members
-public struct NotificationChannelDeletedEvent: ChannelSpecificEvent {
+public final class NotificationChannelDeletedEvent: ChannelSpecificEvent {
/// The cid of the deleted channel
public let cid: ChannelId
@@ -518,6 +595,12 @@ public struct NotificationChannelDeletedEvent: ChannelSpecificEvent {
/// The event timestamp.
public let createdAt: Date
+
+ init(cid: ChannelId, channel: ChatChannel, createdAt: Date) {
+ self.cid = cid
+ self.channel = channel
+ self.createdAt = createdAt
+ }
}
class NotificationChannelDeletedEventDTO: EventDTO {
diff --git a/Sources/StreamChat/WebSocketClient/Events/PollsEvents.swift b/Sources/StreamChat/WebSocketClient/Events/PollsEvents.swift
index d8bd4f2d35..d7d61f69cb 100644
--- a/Sources/StreamChat/WebSocketClient/Events/PollsEvents.swift
+++ b/Sources/StreamChat/WebSocketClient/Events/PollsEvents.swift
@@ -48,13 +48,18 @@ extension PollEventDTO {
}
/// A model representing an event where a poll was closed.
-public struct PollClosedEvent: Event {
+public final class PollClosedEvent: Event {
/// The poll that was closed.
public let poll: Poll
/// The date and time when the event was created.
/// This property is optional and may be `nil`.
public let createdAt: Date?
+
+ init(poll: Poll, createdAt: Date?) {
+ self.poll = poll
+ self.createdAt = createdAt
+ }
}
struct PollClosedEventDTO: PollEventDTO {
@@ -72,13 +77,18 @@ struct PollClosedEventDTO: PollEventDTO {
}
/// A model representing an event where a poll was created.
-public struct PollCreatedEvent: Event {
+public final class PollCreatedEvent: Event {
/// The poll that was created.
public let poll: Poll
/// The date and time when the event was created.
/// This property is optional and may be `nil`.
public let createdAt: Date?
+
+ init(poll: Poll, createdAt: Date?) {
+ self.poll = poll
+ self.createdAt = createdAt
+ }
}
struct PollCreatedEventDTO: PollEventDTO {
@@ -96,13 +106,18 @@ struct PollCreatedEventDTO: PollEventDTO {
}
/// A model representing an event where a poll was deleted.
-public struct PollDeletedEvent: Event {
+public final class PollDeletedEvent: Event {
/// The poll that was deleted.
public let poll: Poll
/// The date and time when the event was created.
/// This property is optional and may be `nil`.
public let createdAt: Date?
+
+ init(poll: Poll, createdAt: Date?) {
+ self.poll = poll
+ self.createdAt = createdAt
+ }
}
struct PollDeletedEventDTO: PollEventDTO {
@@ -120,13 +135,18 @@ struct PollDeletedEventDTO: PollEventDTO {
}
/// A model representing an event where a poll was updated.
-public struct PollUpdatedEvent: Event {
+public final class PollUpdatedEvent: Event {
/// The poll that was updated.
public let poll: Poll
/// The date and time when the event was created.
/// This property is optional and may be `nil`.
public let createdAt: Date?
+
+ init(poll: Poll, createdAt: Date?) {
+ self.poll = poll
+ self.createdAt = createdAt
+ }
}
struct PollUpdatedEventDTO: PollEventDTO {
@@ -144,7 +164,7 @@ struct PollUpdatedEventDTO: PollEventDTO {
}
/// A model representing an event where a vote was casted in a poll.
-public struct PollVoteCastedEvent: Event {
+public final class PollVoteCastedEvent: Event {
/// The vote that was casted.
public let vote: PollVote
@@ -154,6 +174,12 @@ public struct PollVoteCastedEvent: Event {
/// The date and time when the event was created.
/// This property is optional and may be `nil`.
public let createdAt: Date?
+
+ init(vote: PollVote, poll: Poll, createdAt: Date?) {
+ self.vote = vote
+ self.poll = poll
+ self.createdAt = createdAt
+ }
}
struct PollVoteCastedEventDTO: PollVoteEventDTO {
@@ -173,7 +199,7 @@ struct PollVoteCastedEventDTO: PollVoteEventDTO {
}
/// A model representing an event where a vote was changed in a poll.
-public struct PollVoteChangedEvent: Event {
+public final class PollVoteChangedEvent: Event {
/// The vote that was changed.
public let vote: PollVote
@@ -183,6 +209,12 @@ public struct PollVoteChangedEvent: Event {
/// The date and time when the event was created.
/// This property is optional and may be `nil`.
public let createdAt: Date?
+
+ init(vote: PollVote, poll: Poll, createdAt: Date?) {
+ self.vote = vote
+ self.poll = poll
+ self.createdAt = createdAt
+ }
}
struct PollVoteChangedEventDTO: PollVoteEventDTO {
@@ -202,7 +234,7 @@ struct PollVoteChangedEventDTO: PollVoteEventDTO {
}
/// A model representing an event where a vote was removed from a poll.
-public struct PollVoteRemovedEvent: Event {
+public final class PollVoteRemovedEvent: Event {
/// The vote that was removed.
public let vote: PollVote
@@ -212,6 +244,12 @@ public struct PollVoteRemovedEvent: Event {
/// The date and time when the event was created.
/// This property is optional and may be `nil`.
public let createdAt: Date?
+
+ init(vote: PollVote, poll: Poll, createdAt: Date?) {
+ self.vote = vote
+ self.poll = poll
+ self.createdAt = createdAt
+ }
}
struct PollVoteRemovedEventDTO: PollVoteEventDTO {
diff --git a/Sources/StreamChat/WebSocketClient/Events/ReactionEvents.swift b/Sources/StreamChat/WebSocketClient/Events/ReactionEvents.swift
index 153d096d97..be35e5084b 100644
--- a/Sources/StreamChat/WebSocketClient/Events/ReactionEvents.swift
+++ b/Sources/StreamChat/WebSocketClient/Events/ReactionEvents.swift
@@ -5,7 +5,7 @@
import Foundation
/// Triggered a new reaction is added.
-public struct ReactionNewEvent: ChannelSpecificEvent {
+public final class ReactionNewEvent: ChannelSpecificEvent {
/// The use who added a reaction.
public let user: ChatUser
@@ -20,6 +20,14 @@ public struct ReactionNewEvent: ChannelSpecificEvent {
/// The event timestamp.
public let createdAt: Date
+
+ init(user: ChatUser, cid: ChannelId, message: ChatMessage, reaction: ChatMessageReaction, createdAt: Date) {
+ self.user = user
+ self.cid = cid
+ self.message = message
+ self.reaction = reaction
+ self.createdAt = createdAt
+ }
}
class ReactionNewEventDTO: EventDTO {
@@ -61,7 +69,7 @@ class ReactionNewEventDTO: EventDTO {
}
/// Triggered when a reaction is updated.
-public struct ReactionUpdatedEvent: ChannelSpecificEvent {
+public final class ReactionUpdatedEvent: ChannelSpecificEvent {
/// The use who updated a reaction.
public let user: ChatUser
@@ -76,6 +84,14 @@ public struct ReactionUpdatedEvent: ChannelSpecificEvent {
/// The event timestamp.
public let createdAt: Date
+
+ init(user: ChatUser, cid: ChannelId, message: ChatMessage, reaction: ChatMessageReaction, createdAt: Date) {
+ self.user = user
+ self.cid = cid
+ self.message = message
+ self.reaction = reaction
+ self.createdAt = createdAt
+ }
}
class ReactionUpdatedEventDTO: EventDTO {
@@ -117,7 +133,7 @@ class ReactionUpdatedEventDTO: EventDTO {
}
/// Triggered when a reaction is deleted.
-public struct ReactionDeletedEvent: ChannelSpecificEvent {
+public final class ReactionDeletedEvent: ChannelSpecificEvent {
/// The use who deleted a reaction.
public let user: ChatUser
@@ -132,6 +148,14 @@ public struct ReactionDeletedEvent: ChannelSpecificEvent {
/// The event timestamp.
public let createdAt: Date
+
+ init(user: ChatUser, cid: ChannelId, message: ChatMessage, reaction: ChatMessageReaction, createdAt: Date) {
+ self.user = user
+ self.cid = cid
+ self.message = message
+ self.reaction = reaction
+ self.createdAt = createdAt
+ }
}
class ReactionDeletedEventDTO: EventDTO {
diff --git a/Sources/StreamChat/WebSocketClient/Events/ReminderEvents.swift b/Sources/StreamChat/WebSocketClient/Events/ReminderEvents.swift
index 865d6f0e12..79a6e91944 100644
--- a/Sources/StreamChat/WebSocketClient/Events/ReminderEvents.swift
+++ b/Sources/StreamChat/WebSocketClient/Events/ReminderEvents.swift
@@ -5,7 +5,7 @@
import Foundation
/// Triggered when a message reminder is created.
-public class MessageReminderCreatedEvent: Event {
+public final class MessageReminderCreatedEvent: Event {
/// The message ID associated with the reminder.
public let messageId: MessageId
@@ -53,7 +53,7 @@ class ReminderCreatedEventDTO: EventDTO {
}
/// Triggered when a message reminder is updated.
-public class MessageReminderUpdatedEvent: Event {
+public final class MessageReminderUpdatedEvent: Event {
/// The message ID associated with the reminder.
public let messageId: MessageId
@@ -101,7 +101,7 @@ class ReminderUpdatedEventDTO: EventDTO {
}
/// Triggered when a message reminder is deleted.
-public class MessageReminderDeletedEvent: Event {
+public final class MessageReminderDeletedEvent: Event {
/// The message ID associated with the reminder.
public let messageId: MessageId
@@ -153,7 +153,7 @@ class ReminderDeletedEventDTO: EventDTO {
}
/// Triggered when a reminder is due and a notification should be shown.
-public class MessageReminderDueEvent: Event {
+public final class MessageReminderDueEvent: Event {
/// The message ID associated with the reminder.
public let messageId: MessageId
diff --git a/Sources/StreamChat/WebSocketClient/Events/ThreadEvents.swift b/Sources/StreamChat/WebSocketClient/Events/ThreadEvents.swift
index f896b63f90..06292937ce 100644
--- a/Sources/StreamChat/WebSocketClient/Events/ThreadEvents.swift
+++ b/Sources/StreamChat/WebSocketClient/Events/ThreadEvents.swift
@@ -5,7 +5,7 @@
import Foundation
/// Triggered when a new message is sent to a thread.
-public struct ThreadMessageNewEvent: Event {
+public final class ThreadMessageNewEvent: Event {
/// The reply that was sent.
public let message: ChatMessage
@@ -20,6 +20,13 @@ public struct ThreadMessageNewEvent: Event {
/// The event timestamp.
public let createdAt: Date
+
+ init(message: ChatMessage, channel: ChatChannel, unreadCount: UnreadCount, createdAt: Date) {
+ self.message = message
+ self.channel = channel
+ self.unreadCount = unreadCount
+ self.createdAt = createdAt
+ }
}
class ThreadMessageNewEventDTO: EventDTO {
@@ -56,12 +63,17 @@ class ThreadMessageNewEventDTO: EventDTO {
}
/// Triggered when a thread is updated
-public struct ThreadUpdatedEvent: Event {
+public final class ThreadUpdatedEvent: Event {
/// The updated user
public let thread: ChatThread
/// The event timestamp
public let createdAt: Date?
+
+ init(thread: ChatThread, createdAt: Date?) {
+ self.thread = thread
+ self.createdAt = createdAt
+ }
}
class ThreadUpdatedEventDTO: EventDTO {
diff --git a/Sources/StreamChat/WebSocketClient/Events/TypingEvent.swift b/Sources/StreamChat/WebSocketClient/Events/TypingEvent.swift
index 714b96f2d9..e71abe3fb4 100644
--- a/Sources/StreamChat/WebSocketClient/Events/TypingEvent.swift
+++ b/Sources/StreamChat/WebSocketClient/Events/TypingEvent.swift
@@ -5,7 +5,7 @@
import Foundation
/// Triggered when user starts/stops typing in a channel.
-public struct TypingEvent: ChannelSpecificEvent {
+public final class TypingEvent: ChannelSpecificEvent {
/// The flag saying if typing is started/stopped.
public let isTyping: Bool
@@ -23,6 +23,14 @@ public struct TypingEvent: ChannelSpecificEvent {
/// `true` if typing event happened in the message thread.
public var isThread: Bool { parentId != nil }
+
+ init(isTyping: Bool, cid: ChannelId, user: ChatUser, parentId: MessageId?, createdAt: Date) {
+ self.isTyping = isTyping
+ self.cid = cid
+ self.user = user
+ self.parentId = parentId
+ self.createdAt = createdAt
+ }
}
class TypingEventDTO: EventDTO {
@@ -59,7 +67,7 @@ class TypingEventDTO: EventDTO {
/// A special event type which is only emitted by the SDK and never the backend.
/// This event is emitted by `TypingStartCleanupMiddleware` to signal that a typing event
/// must be cleaned up, due to timeout of that event.
-public struct CleanUpTypingEvent: Event {
+public final class CleanUpTypingEvent: Event {
public let cid: ChannelId
public let userId: UserId
diff --git a/Sources/StreamChat/WebSocketClient/Events/UserEvents.swift b/Sources/StreamChat/WebSocketClient/Events/UserEvents.swift
index ee17af1049..dfeb39eb9a 100644
--- a/Sources/StreamChat/WebSocketClient/Events/UserEvents.swift
+++ b/Sources/StreamChat/WebSocketClient/Events/UserEvents.swift
@@ -5,12 +5,17 @@
import Foundation
/// Triggered when user status changes (eg. online, offline, away, etc.)
-public struct UserPresenceChangedEvent: Event {
+public final class UserPresenceChangedEvent: Event {
/// The user the status changed for
public let user: ChatUser
/// The event timestamp
public let createdAt: Date?
+
+ init(user: ChatUser, createdAt: Date?) {
+ self.user = user
+ self.createdAt = createdAt
+ }
}
class UserPresenceChangedEventDTO: EventDTO {
@@ -35,12 +40,17 @@ class UserPresenceChangedEventDTO: EventDTO {
}
/// Triggered when user is updated
-public struct UserUpdatedEvent: Event {
+public final class UserUpdatedEvent: Event {
/// The updated user
public let user: ChatUser
/// The event timestamp
public let createdAt: Date?
+
+ init(user: ChatUser, createdAt: Date?) {
+ self.user = user
+ self.createdAt = createdAt
+ }
}
class UserUpdatedEventDTO: EventDTO {
@@ -67,7 +77,7 @@ class UserUpdatedEventDTO: EventDTO {
// MARK: - User Watching
/// Triggered when a user starts/stops watching a channel
-public struct UserWatchingEvent: ChannelSpecificEvent {
+public final class UserWatchingEvent: ChannelSpecificEvent {
/// The channel identifier a user started/stopped watching
public let cid: ChannelId
@@ -82,6 +92,14 @@ public struct UserWatchingEvent: ChannelSpecificEvent {
/// The flag saying if watching was started or stopped
public let isStarted: Bool
+
+ init(cid: ChannelId, createdAt: Date, user: ChatUser, watcherCount: Int, isStarted: Bool) {
+ self.cid = cid
+ self.createdAt = createdAt
+ self.user = user
+ self.watcherCount = watcherCount
+ self.isStarted = isStarted
+ }
}
class UserWatchingEventDTO: EventDTO {
@@ -117,15 +135,20 @@ class UserWatchingEventDTO: EventDTO {
// MARK: - User Ban
/// Triggered when user is banned not in a specific channel but globally.
-public struct UserGloballyBannedEvent: Event {
+public final class UserGloballyBannedEvent: Event {
/// The banned user
public let user: ChatUser
/// The event timestamp
public let createdAt: Date
+
+ init(user: ChatUser, createdAt: Date) {
+ self.user = user
+ self.createdAt = createdAt
+ }
}
-struct UserGloballyBannedEventDTO: EventDTO {
+class UserGloballyBannedEventDTO: EventDTO {
let user: UserPayload
let createdAt: Date
let payload: EventPayload
@@ -147,7 +170,7 @@ struct UserGloballyBannedEventDTO: EventDTO {
}
/// Triggered when user is banned in a specific channel
-public struct UserBannedEvent: ChannelSpecificEvent {
+public final class UserBannedEvent: ChannelSpecificEvent {
/// The channel identifer user is banned at.
public let cid: ChannelId
@@ -168,6 +191,16 @@ public struct UserBannedEvent: ChannelSpecificEvent {
/// A boolean value indicating if the ban is a shadowed ban or not.
public let isShadowBan: Bool?
+
+ init(cid: ChannelId, user: ChatUser, ownerId: UserId, createdAt: Date?, reason: String?, expiredAt: Date?, isShadowBan: Bool?) {
+ self.cid = cid
+ self.user = user
+ self.ownerId = ownerId
+ self.createdAt = createdAt
+ self.reason = reason
+ self.expiredAt = expiredAt
+ self.isShadowBan = isShadowBan
+ }
}
class UserBannedEventDTO: EventDTO {
@@ -207,15 +240,20 @@ class UserBannedEventDTO: EventDTO {
}
/// Triggered when user is removed from global ban.
-public struct UserGloballyUnbannedEvent: Event {
+public final class UserGloballyUnbannedEvent: Event {
/// The unbanned user.
public let user: ChatUser
/// The event timestamp
public let createdAt: Date
+
+ init(user: ChatUser, createdAt: Date) {
+ self.user = user
+ self.createdAt = createdAt
+ }
}
-struct UserGloballyUnbannedEventDTO: EventDTO {
+class UserGloballyUnbannedEventDTO: EventDTO {
let user: UserPayload
let createdAt: Date
let payload: EventPayload
@@ -237,7 +275,7 @@ struct UserGloballyUnbannedEventDTO: EventDTO {
}
/// Triggered when banned user is unbanned in a specific channel
-public struct UserUnbannedEvent: ChannelSpecificEvent {
+public final class UserUnbannedEvent: ChannelSpecificEvent {
/// The channel identifer user is unbanned at.
public let cid: ChannelId
@@ -246,6 +284,12 @@ public struct UserUnbannedEvent: ChannelSpecificEvent {
/// The event timestamp
public let createdAt: Date?
+
+ init(cid: ChannelId, user: ChatUser, createdAt: Date?) {
+ self.cid = cid
+ self.user = user
+ self.createdAt = createdAt
+ }
}
class UserUnbannedEventDTO: EventDTO {
@@ -273,7 +317,7 @@ class UserUnbannedEventDTO: EventDTO {
}
/// Triggered when the messages of a banned user should be deleted.
-public struct UserMessagesDeletedEvent: Event {
+public final class UserMessagesDeletedEvent: Event {
/// The banned user.
public let user: ChatUser
@@ -282,6 +326,12 @@ public struct UserMessagesDeletedEvent: Event {
/// The event timestamp
public let createdAt: Date
+
+ init(user: ChatUser, hardDelete: Bool, createdAt: Date) {
+ self.user = user
+ self.hardDelete = hardDelete
+ self.createdAt = createdAt
+ }
}
class UserMessagesDeletedEventDTO: EventDTO {
From cd730c6fc639f795988b94f67543206e249de0ba Mon Sep 17 00:00:00 2001
From: Toomas Vahter
Date: Tue, 18 Nov 2025 12:37:31 +0200
Subject: [PATCH 09/10] Add ChatClientConfig.isAutomaticSyncOnReconnectEnabled
for toggling automatic syncing (#3879)
---
CHANGELOG.md | 2 +
.../StreamChat/Config/ChatClientConfig.swift | 24 +++++++
.../Repositories/SyncRepository.swift | 71 +++++++++++--------
.../Repositories/SyncRepository_Tests.swift | 58 +++++++++++++++
4 files changed, 124 insertions(+), 31 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index caa7095bc6..68971861e4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
# Upcoming
## StreamChat
+### ✅ Added
+- Add `ChatClientConfig.isAutomaticSyncOnReconnectEnabled` for toggling automatic syncing [#3879](https://github.com/GetStream/stream-chat-swift/pull/3879)
### ⚡️ Performance
- Reduce SDK size by converting Events from structs to classes [#3878](https://github.com/GetStream/stream-chat-swift/pull/3878)
### 🔄 Changed
diff --git a/Sources/StreamChat/Config/ChatClientConfig.swift b/Sources/StreamChat/Config/ChatClientConfig.swift
index 7ce758736e..3f45045bca 100644
--- a/Sources/StreamChat/Config/ChatClientConfig.swift
+++ b/Sources/StreamChat/Config/ChatClientConfig.swift
@@ -203,6 +203,30 @@ public struct ChatClientConfig {
/// How many hours the unsent actions should be queued for sending when the internet connection is available.
public var queuedActionsMaxHoursThreshold: Int = 12
+
+ /// Specifies, if local data is updated with subscribing to web-socket events after reconnection.
+ ///
+ /// When turning this off it is up for SDK user to observe web-socket connection state for reconnection
+ /// and syncing data by calling, for example, controller's `synchronize` or state-layer's `get` methods.
+ /// Note that, these calls fetch the latest state and also subscribe to web-socket events if query's `watch` is true.
+ ///
+ /// Web-socket connection status can be updated either with subscribe method or through ``ChatConnectionController``.
+ /// ```swift
+ /// connectionCancellable = chatClient.subscribe(
+ /// toEvent: ConnectionStatusUpdated.self,
+ /// handler: { connectionEvent in
+ /// switch connectionEvent.connectionStatus {
+ /// case .connected:
+ /// // Data can be updated and watching can be resumed
+ /// default:
+ /// break
+ /// }
+ /// }
+ /// )
+ /// ```
+ ///
+ /// - SeeAlso: Query option``QueryOptions/watch`` used by ``ChannelListQuery`` and ``ChannelQuery``.
+ public var isAutomaticSyncOnReconnectEnabled = true
public init(
apiKey: APIKey
diff --git a/Sources/StreamChat/Repositories/SyncRepository.swift b/Sources/StreamChat/Repositories/SyncRepository.swift
index a3671ff913..0fed57c2e8 100644
--- a/Sources/StreamChat/Repositories/SyncRepository.swift
+++ b/Sources/StreamChat/Repositories/SyncRepository.swift
@@ -169,7 +169,6 @@ class SyncRepository {
let context = SyncContext(lastSyncAt: lastSyncAt)
var operations: [Operation] = []
let start = CFAbsoluteTimeGetCurrent()
- log.info("Starting to refresh offline state", subsystems: .offlineSupport)
//
// Recovery mode operations (other API requests are paused)
@@ -185,36 +184,46 @@ class SyncRepository {
//
// Background mode operations
//
-
- /// 1. Collect all the **active** channel ids
- operations.append(ActiveChannelIdsOperation(syncRepository: self, context: context))
-
- // 2. Refresh channel lists
- operations.append(contentsOf: activeChannelLists.allObjects.map { RefreshChannelListOperation(channelList: $0, context: context) })
- operations.append(contentsOf: activeChannelListControllers.allObjects.map { RefreshChannelListOperation(controller: $0, context: context) })
-
- // 3. /sync (for channels what not part of active channel lists)
- operations.append(SyncEventsOperation(syncRepository: self, context: context, recovery: false))
-
- // 4. Re-watch channels what we were watching before disconnect
- // Needs to be done explicitly after reconnection, otherwise SDK users need to handle connection changes
- operations.append(contentsOf: activeChannelControllers.allObjects.map {
- WatchChannelOperation(controller: $0, context: context, recovery: false)
- })
- operations.append(contentsOf: activeChats.allObjects.map {
- WatchChannelOperation(chat: $0, context: context)
- })
- operations.append(contentsOf: activeLivestreamControllers.allObjects.map {
- WatchChannelOperation(livestreamController: $0, context: context, recovery: false)
- })
-
- operations.append(BlockOperation(block: {
- let duration = CFAbsoluteTimeGetCurrent() - start
- log.info("Finished refreshing offline state (\(context.synchedChannelIds.count) channels in \(String(format: "%.1f", duration)) seconds)", subsystems: .offlineSupport)
- DispatchQueue.main.async {
- completion()
- }
- }))
+ if config.isAutomaticSyncOnReconnectEnabled {
+ log.info("Starting to refresh offline state", subsystems: .offlineSupport)
+
+ /// 1. Collect all the **active** channel ids
+ operations.append(ActiveChannelIdsOperation(syncRepository: self, context: context))
+
+ // 2. Refresh channel lists
+ operations.append(contentsOf: activeChannelLists.allObjects.map { RefreshChannelListOperation(channelList: $0, context: context) })
+ operations.append(contentsOf: activeChannelListControllers.allObjects.map { RefreshChannelListOperation(controller: $0, context: context) })
+
+ // 3. /sync (for channels what not part of active channel lists)
+ operations.append(SyncEventsOperation(syncRepository: self, context: context, recovery: false))
+
+ // 4. Re-watch channels what we were watching before disconnect
+ // Needs to be done explicitly after reconnection, otherwise SDK users need to handle connection changes
+ operations.append(contentsOf: activeChannelControllers.allObjects.map {
+ WatchChannelOperation(controller: $0, context: context, recovery: false)
+ })
+ operations.append(contentsOf: activeChats.allObjects.map {
+ WatchChannelOperation(chat: $0, context: context)
+ })
+ operations.append(contentsOf: activeLivestreamControllers.allObjects.map {
+ WatchChannelOperation(livestreamController: $0, context: context, recovery: false)
+ })
+
+ operations.append(BlockOperation(block: {
+ let duration = CFAbsoluteTimeGetCurrent() - start
+ log.info("Finished refreshing offline state (\(context.synchedChannelIds.count) channels in \(String(format: "%.1f", duration)) seconds)", subsystems: .offlineSupport)
+ DispatchQueue.main.async {
+ completion()
+ }
+ }))
+ } else {
+ // When automatic sync is disabled, still call completion after recovery operations finish
+ operations.append(BlockOperation(block: {
+ DispatchQueue.main.async {
+ completion()
+ }
+ }))
+ }
var previousOperation: Operation?
operations.reversed().forEach { operation in
diff --git a/Tests/StreamChatTests/Repositories/SyncRepository_Tests.swift b/Tests/StreamChatTests/Repositories/SyncRepository_Tests.swift
index 0c776d8f8f..7af570801c 100644
--- a/Tests/StreamChatTests/Repositories/SyncRepository_Tests.swift
+++ b/Tests/StreamChatTests/Repositories/SyncRepository_Tests.swift
@@ -277,6 +277,64 @@ class SyncRepository_Tests: XCTestCase {
waitForSyncLocalStateRun()
}
+ func test_syncLocalState_isAutomaticSyncOnReconnectEnabled_false_noOperationsRun() throws {
+ var config = ChatClientConfig(apiKeyString: .unique)
+ config.isLocalStorageEnabled = true
+ config.isAutomaticSyncOnReconnectEnabled = false
+ let client = ChatClient_Mock(config: config)
+ repository = SyncRepository(
+ config: client.config,
+ offlineRequestsRepository: offlineRequestsRepository,
+ eventNotificationCenter: repository.eventNotificationCenter,
+ database: database,
+ apiClient: apiClient,
+ channelListUpdater: channelListUpdater
+ )
+
+ let cid = ChannelId.unique
+ try prepareForSyncLocalStorage(
+ createUser: true,
+ lastSynchedEventDate: Date().addingTimeInterval(-3600),
+ createChannel: true,
+ cid: cid
+ )
+
+ // Set up active controllers and chats that would normally trigger sync operations
+ let chatController = ChatChannelController_Spy(client: client)
+ chatController.state = .remoteDataFetched
+ repository.startTrackingChannelController(chatController)
+
+ let chat = Chat_Mock(
+ chatClient: client,
+ channelQuery: .init(cid: Chat_Mock.cid),
+ channelListQuery: nil
+ )
+ repository.startTrackingChat(chat)
+
+ let chatListController = ChatChannelListController_Mock(query: .init(filter: .exists(.cid)), client: client)
+ chatListController.state_mock = .remoteDataFetched
+ chatListController.channels_mock = [.mock(cid: cid)]
+ repository.startTrackingChannelListController(chatListController)
+
+ // WHEN: syncLocalState is called
+ let expectation = expectation(description: "syncLocalState completion")
+ repository.syncLocalState {
+ expectation.fulfill()
+ }
+ waitForExpectations(timeout: defaultTimeout, handler: nil)
+
+ // THEN: No background mode operations should run
+ // No /sync API calls should be made
+ XCTAssertEqual(apiClient.request_allRecordedCalls.count, 0)
+ // No refreshLoadedChannels calls should be made
+ XCTAssertNotCall("refreshLoadedChannels(completion:)", on: chatListController)
+ // No watch() calls should be made
+ XCTAssertNotCall("watch()", on: chat)
+ // Recovery mode operations may still run if isLocalStorageEnabled is true
+ XCTAssertCall("runQueuedRequests(completion:)", on: offlineRequestsRepository, times: 1)
+ XCTAssertCall("exitRecoveryMode()", on: apiClient)
+ }
+
// MARK: - Queue offline requests
func test_queueOfflineRequest_localStorageDisabled() {
From 7fcf18dae7ea605c2f9d21750a72b8276ac40892 Mon Sep 17 00:00:00 2001
From: Stream Bot
Date: Tue, 18 Nov 2025 11:08:23 +0000
Subject: [PATCH 10/10] Bump 4.93.0
---
CHANGELOG.md | 5 +++++
README.md | 2 +-
Sources/StreamChat/Generated/SystemEnvironment+Version.swift | 2 +-
Sources/StreamChat/Info.plist | 2 +-
Sources/StreamChatUI/Info.plist | 2 +-
StreamChat-XCFramework.podspec | 2 +-
StreamChat.podspec | 2 +-
StreamChatArtifacts.json | 2 +-
StreamChatUI-XCFramework.podspec | 2 +-
StreamChatUI.podspec | 2 +-
10 files changed, 14 insertions(+), 9 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 68971861e4..84d84be06b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
# Upcoming
+### 🔄 Changed
+
+# [4.93.0](https://github.com/GetStream/stream-chat-swift/releases/tag/4.93.0)
+_November 18, 2025_
+
## StreamChat
### ✅ Added
- Add `ChatClientConfig.isAutomaticSyncOnReconnectEnabled` for toggling automatic syncing [#3879](https://github.com/GetStream/stream-chat-swift/pull/3879)
diff --git a/README.md b/README.md
index b56120f208..f739b83548 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
-
+
diff --git a/Sources/StreamChat/Generated/SystemEnvironment+Version.swift b/Sources/StreamChat/Generated/SystemEnvironment+Version.swift
index fc8290c558..5d2a7a0017 100644
--- a/Sources/StreamChat/Generated/SystemEnvironment+Version.swift
+++ b/Sources/StreamChat/Generated/SystemEnvironment+Version.swift
@@ -7,5 +7,5 @@ import Foundation
extension SystemEnvironment {
/// A Stream Chat version.
- public static let version: String = "4.93.0-SNAPSHOT"
+ public static let version: String = "4.93.0"
}
diff --git a/Sources/StreamChat/Info.plist b/Sources/StreamChat/Info.plist
index 1d5fb98922..a7bad28254 100644
--- a/Sources/StreamChat/Info.plist
+++ b/Sources/StreamChat/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 4.92.0
+ 4.93.0
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
diff --git a/Sources/StreamChatUI/Info.plist b/Sources/StreamChatUI/Info.plist
index 1d5fb98922..a7bad28254 100644
--- a/Sources/StreamChatUI/Info.plist
+++ b/Sources/StreamChatUI/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 4.92.0
+ 4.93.0
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
diff --git a/StreamChat-XCFramework.podspec b/StreamChat-XCFramework.podspec
index 6b29987002..ca004cff99 100644
--- a/StreamChat-XCFramework.podspec
+++ b/StreamChat-XCFramework.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'StreamChat-XCFramework'
- spec.version = '4.92.0'
+ spec.version = '4.93.0'
spec.summary = 'StreamChat iOS Client'
spec.description = 'stream-chat-swift is the official Swift client for Stream Chat, a service for building chat applications.'
diff --git a/StreamChat.podspec b/StreamChat.podspec
index 2cf51e5b3d..5769b60ab4 100644
--- a/StreamChat.podspec
+++ b/StreamChat.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'StreamChat'
- spec.version = '4.92.0'
+ spec.version = '4.93.0'
spec.summary = 'StreamChat iOS Chat Client'
spec.description = 'stream-chat-swift is the official Swift client for Stream Chat, a service for building chat applications.'
diff --git a/StreamChatArtifacts.json b/StreamChatArtifacts.json
index 22ee5f1ed4..f56eb4699a 100644
--- a/StreamChatArtifacts.json
+++ b/StreamChatArtifacts.json
@@ -1 +1 @@
-{"4.7.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.7.0/StreamChat-All.zip","4.8.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.8.0/StreamChat-All.zip","4.9.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.9.0/StreamChat-All.zip","4.10.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.10.0/StreamChat-All.zip","4.10.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.10.1/StreamChat-All.zip","4.11.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.11.0/StreamChat-All.zip","4.12.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.12.0/StreamChat-All.zip","4.13.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.13.0/StreamChat-All.zip","4.13.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.13.1/StreamChat-All.zip","4.14.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.14.0/StreamChat-All.zip","4.15.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.15.0/StreamChat-All.zip","4.15.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.15.1/StreamChat-All.zip","4.16.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.16.0/StreamChat-All.zip","4.17.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.17.0/StreamChat-All.zip","4.18.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.18.0/StreamChat-All.zip","4.19.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.19.0/StreamChat-All.zip","4.20.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.20.0/StreamChat-All.zip","4.21.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.0/StreamChat-All.zip","4.21.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.1/StreamChat-All.zip","4.21.2":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.2/StreamChat-All.zip","4.22.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.22.0/StreamChat-All.zip","4.23.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.23.0/StreamChat-All.zip","4.24.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.24.0/StreamChat-All.zip","4.24.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.24.1/StreamChat-All.zip","4.25.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.25.0/StreamChat-All.zip","4.25.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.25.1/StreamChat-All.zip","4.26.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.26.0/StreamChat-All.zip","4.27.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.27.0/StreamChat-All.zip","4.27.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.27.1/StreamChat-All.zip","4.28.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.28.0/StreamChat-All.zip","4.29.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.29.0/StreamChat-All.zip","4.30.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.30.0/StreamChat-All.zip","4.31.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.31.0/StreamChat-All.zip","4.32.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.32.0/StreamChat-All.zip","4.33.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.33.0/StreamChat-All.zip","4.34.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.34.0/StreamChat-All.zip","4.35.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.0/StreamChat-All.zip","4.35.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.1/StreamChat-All.zip","4.35.2":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.2/StreamChat-All.zip","4.36.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.36.0/StreamChat-All.zip","4.37.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.37.0/StreamChat-All.zip","4.37.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.37.1/StreamChat-All.zip","4.38.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.38.0/StreamChat-All.zip","4.39.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.39.0/StreamChat-All.zip","4.40.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.40.0/StreamChat-All.zip","4.41.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.41.0/StreamChat-All.zip","4.42.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.42.0/StreamChat-All.zip","4.43.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.43.0/StreamChat-All.zip","4.44.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.44.0/StreamChat-All.zip","4.45.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.45.0/StreamChat-All.zip","4.46.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.46.0/StreamChat-All.zip","4.47.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.47.0/StreamChat-All.zip","4.47.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.47.1/StreamChat-All.zip","4.48.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.48.0/StreamChat-All.zip","4.48.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.48.1/StreamChat-All.zip","4.49.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.49.0/StreamChat-All.zip","4.50.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.50.0/StreamChat-All.zip","4.51.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.51.0/StreamChat-All.zip","4.52.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.52.0/StreamChat-All.zip","4.53.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.53.0/StreamChat-All.zip","4.54.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.54.0/StreamChat-All.zip","4.55.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.55.0/StreamChat-All.zip","4.56.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.56.0/StreamChat-All.zip","4.56.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.56.1/StreamChat-All.zip","4.57.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.57.0/StreamChat-All.zip","4.58.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.58.0/StreamChat-All.zip","4.59.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.59.0/StreamChat-All.zip","4.60.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.60.0/StreamChat-All.zip","4.61.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.61.0/StreamChat-All.zip","4.62.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.62.0/StreamChat-All.zip","4.63.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.63.0/StreamChat-All.zip","4.64.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.64.0/StreamChat-All.zip","4.65.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.65.0/StreamChat-All.zip","4.66.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.66.0/StreamChat-All.zip","4.67.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.67.0/StreamChat-All.zip","4.68.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.68.0/StreamChat-All.zip","4.69.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.69.0/StreamChat-All.zip","4.70.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.70.0/StreamChat-All.zip","4.71.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.71.0/StreamChat-All.zip","4.72.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.72.0/StreamChat-All.zip","4.73.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.73.0/StreamChat-All.zip","4.74.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.74.0/StreamChat-All.zip","4.75.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.75.0/StreamChat-All.zip","4.76.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.76.0/StreamChat-All.zip","4.77.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.77.0/StreamChat-All.zip","4.78.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.78.0/StreamChat-All.zip","4.79.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.79.0/StreamChat-All.zip","4.79.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.79.1/StreamChat-All.zip","4.80.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.80.0/StreamChat-All.zip","4.81.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.81.0/StreamChat-All.zip","4.82.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.82.0/StreamChat-All.zip","4.83.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.83.0/StreamChat-All.zip","4.84.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.84.0/StreamChat-All.zip","4.85.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.85.0/StreamChat-All.zip","4.86.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.86.0/StreamChat-All.zip","4.87.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.87.0/StreamChat-All.zip","4.88.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.88.0/StreamChat-All.zip","4.89.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.89.0/StreamChat-All.zip","4.90.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.90.0/StreamChat-All.zip","4.91.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.91.0/StreamChat-All.zip","4.92.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.92.0/StreamChat-All.zip"}
\ No newline at end of file
+{"4.7.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.7.0/StreamChat-All.zip","4.8.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.8.0/StreamChat-All.zip","4.9.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.9.0/StreamChat-All.zip","4.10.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.10.0/StreamChat-All.zip","4.10.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.10.1/StreamChat-All.zip","4.11.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.11.0/StreamChat-All.zip","4.12.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.12.0/StreamChat-All.zip","4.13.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.13.0/StreamChat-All.zip","4.13.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.13.1/StreamChat-All.zip","4.14.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.14.0/StreamChat-All.zip","4.15.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.15.0/StreamChat-All.zip","4.15.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.15.1/StreamChat-All.zip","4.16.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.16.0/StreamChat-All.zip","4.17.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.17.0/StreamChat-All.zip","4.18.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.18.0/StreamChat-All.zip","4.19.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.19.0/StreamChat-All.zip","4.20.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.20.0/StreamChat-All.zip","4.21.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.0/StreamChat-All.zip","4.21.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.1/StreamChat-All.zip","4.21.2":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.2/StreamChat-All.zip","4.22.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.22.0/StreamChat-All.zip","4.23.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.23.0/StreamChat-All.zip","4.24.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.24.0/StreamChat-All.zip","4.24.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.24.1/StreamChat-All.zip","4.25.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.25.0/StreamChat-All.zip","4.25.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.25.1/StreamChat-All.zip","4.26.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.26.0/StreamChat-All.zip","4.27.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.27.0/StreamChat-All.zip","4.27.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.27.1/StreamChat-All.zip","4.28.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.28.0/StreamChat-All.zip","4.29.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.29.0/StreamChat-All.zip","4.30.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.30.0/StreamChat-All.zip","4.31.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.31.0/StreamChat-All.zip","4.32.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.32.0/StreamChat-All.zip","4.33.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.33.0/StreamChat-All.zip","4.34.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.34.0/StreamChat-All.zip","4.35.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.0/StreamChat-All.zip","4.35.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.1/StreamChat-All.zip","4.35.2":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.2/StreamChat-All.zip","4.36.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.36.0/StreamChat-All.zip","4.37.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.37.0/StreamChat-All.zip","4.37.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.37.1/StreamChat-All.zip","4.38.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.38.0/StreamChat-All.zip","4.39.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.39.0/StreamChat-All.zip","4.40.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.40.0/StreamChat-All.zip","4.41.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.41.0/StreamChat-All.zip","4.42.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.42.0/StreamChat-All.zip","4.43.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.43.0/StreamChat-All.zip","4.44.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.44.0/StreamChat-All.zip","4.45.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.45.0/StreamChat-All.zip","4.46.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.46.0/StreamChat-All.zip","4.47.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.47.0/StreamChat-All.zip","4.47.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.47.1/StreamChat-All.zip","4.48.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.48.0/StreamChat-All.zip","4.48.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.48.1/StreamChat-All.zip","4.49.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.49.0/StreamChat-All.zip","4.50.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.50.0/StreamChat-All.zip","4.51.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.51.0/StreamChat-All.zip","4.52.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.52.0/StreamChat-All.zip","4.53.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.53.0/StreamChat-All.zip","4.54.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.54.0/StreamChat-All.zip","4.55.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.55.0/StreamChat-All.zip","4.56.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.56.0/StreamChat-All.zip","4.56.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.56.1/StreamChat-All.zip","4.57.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.57.0/StreamChat-All.zip","4.58.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.58.0/StreamChat-All.zip","4.59.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.59.0/StreamChat-All.zip","4.60.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.60.0/StreamChat-All.zip","4.61.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.61.0/StreamChat-All.zip","4.62.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.62.0/StreamChat-All.zip","4.63.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.63.0/StreamChat-All.zip","4.64.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.64.0/StreamChat-All.zip","4.65.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.65.0/StreamChat-All.zip","4.66.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.66.0/StreamChat-All.zip","4.67.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.67.0/StreamChat-All.zip","4.68.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.68.0/StreamChat-All.zip","4.69.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.69.0/StreamChat-All.zip","4.70.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.70.0/StreamChat-All.zip","4.71.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.71.0/StreamChat-All.zip","4.72.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.72.0/StreamChat-All.zip","4.73.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.73.0/StreamChat-All.zip","4.74.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.74.0/StreamChat-All.zip","4.75.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.75.0/StreamChat-All.zip","4.76.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.76.0/StreamChat-All.zip","4.77.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.77.0/StreamChat-All.zip","4.78.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.78.0/StreamChat-All.zip","4.79.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.79.0/StreamChat-All.zip","4.79.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.79.1/StreamChat-All.zip","4.80.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.80.0/StreamChat-All.zip","4.81.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.81.0/StreamChat-All.zip","4.82.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.82.0/StreamChat-All.zip","4.83.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.83.0/StreamChat-All.zip","4.84.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.84.0/StreamChat-All.zip","4.85.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.85.0/StreamChat-All.zip","4.86.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.86.0/StreamChat-All.zip","4.87.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.87.0/StreamChat-All.zip","4.88.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.88.0/StreamChat-All.zip","4.89.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.89.0/StreamChat-All.zip","4.90.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.90.0/StreamChat-All.zip","4.91.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.91.0/StreamChat-All.zip","4.92.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.92.0/StreamChat-All.zip","4.93.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.93.0/StreamChat-All.zip"}
\ No newline at end of file
diff --git a/StreamChatUI-XCFramework.podspec b/StreamChatUI-XCFramework.podspec
index 581e3124d4..435d14e6f1 100644
--- a/StreamChatUI-XCFramework.podspec
+++ b/StreamChatUI-XCFramework.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'StreamChatUI-XCFramework'
- spec.version = '4.92.0'
+ spec.version = '4.93.0'
spec.summary = 'StreamChat UI Components'
spec.description = 'StreamChatUI SDK offers flexible UI components able to display data provided by StreamChat SDK.'
diff --git a/StreamChatUI.podspec b/StreamChatUI.podspec
index ea2bb78e39..5bad063de7 100644
--- a/StreamChatUI.podspec
+++ b/StreamChatUI.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "StreamChatUI"
- spec.version = "4.92.0"
+ spec.version = "4.93.0"
spec.summary = "StreamChat UI Components"
spec.description = "StreamChatUI SDK offers flexible UI components able to display data provided by StreamChat SDK."