diff --git a/CHANGELOG.md b/CHANGELOG.md index 3871d3679d9..69e8097f8e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Expose `notificationsMuted` in `ChatChannelMember` [#3111](https://github.com/GetStream/stream-chat-swift/pull/3111) ### 🐞 Fixed - Fix saving reaction counts for messages [#3109](https://github.com/GetStream/stream-chat-swift/pull/3109) +### 🔄 Changed +- Deprecates `ChatClientConfig.maxAttachmentSize` in favour of defining the value from Stream's Dashboard [#3105](https://github.com/GetStream/stream-chat-swift/pull/3105) ## StreamChatUI +### ✅ Added +- Validates file size limit per attachment type defined in Stream's Dashboard [#3105](https://github.com/GetStream/stream-chat-swift/pull/3105) ### 🐞 Fixed - Fix support for markdown ordered list with all numbers [#3090](https://github.com/GetStream/stream-chat-swift/pull/3090) - Fix support for markdown italic and bold styles inside snake-styled text [#3094](https://github.com/GetStream/stream-chat-swift/pull/3094) diff --git a/Sources/StreamChat/APIClient/Endpoints/Payloads/AppSettingsPayload.swift b/Sources/StreamChat/APIClient/Endpoints/Payloads/AppSettingsPayload.swift index 24dde4eb6e9..8c793166384 100644 --- a/Sources/StreamChat/APIClient/Endpoints/Payloads/AppSettingsPayload.swift +++ b/Sources/StreamChat/APIClient/Endpoints/Payloads/AppSettingsPayload.swift @@ -21,6 +21,7 @@ struct AppSettingsPayload: Decodable { let blockedFileExtensions: [String] let allowedMimeTypes: [String] let blockedMimeTypes: [String] + let sizeLimit: Int64? } } @@ -48,5 +49,6 @@ extension AppSettingsPayload.UploadConfigPayload { case blockedFileExtensions = "blocked_file_extensions" case allowedMimeTypes = "allowed_mime_types" case blockedMimeTypes = "blocked_mime_types" + case sizeLimit = "size_limit" } } diff --git a/Sources/StreamChat/Config/ChatClientConfig.swift b/Sources/StreamChat/Config/ChatClientConfig.swift index bf083502bf2..f239e958cdf 100644 --- a/Sources/StreamChat/Config/ChatClientConfig.swift +++ b/Sources/StreamChat/Config/ChatClientConfig.swift @@ -127,6 +127,7 @@ public struct ChatClientConfig { /// Returns max possible attachment size in bytes. /// By default the value is taken from `CDNClient.maxAttachmentSize` type. /// But it can be overridden by setting a value here. + @available(*, deprecated, message: "The max attachment size can now be set from the Stream's Dashboard App Settings. It supports setting a size limit per attachment type.") public var maxAttachmentSize: Int64 { // TODO: For v5 the maxAttachmentSize should be responsibility of the UI SDK. // Since this is not even used in the StreamChat LLC SDK. diff --git a/Sources/StreamChat/Models/AppSettings.swift b/Sources/StreamChat/Models/AppSettings.swift index baa60b0027c..6f9b8222be5 100644 --- a/Sources/StreamChat/Models/AppSettings.swift +++ b/Sources/StreamChat/Models/AppSettings.swift @@ -19,13 +19,16 @@ public struct AppSettings { public struct UploadConfig { /// The allowed file extensions. - public var allowedFileExtensions: [String] + public let allowedFileExtensions: [String] /// The blocked file extensions. - public var blockedFileExtensions: [String] + public let blockedFileExtensions: [String] /// The allowed mime types. - public var allowedMimeTypes: [String] + public let allowedMimeTypes: [String] /// The blocked mime types. - public var blockedMimeTypes: [String] + public let blockedMimeTypes: [String] + /// The file size limit allowed in Bytes. + /// This value is configurable from Stream's Dashboard App Settings. + public let sizeLimitInBytes: Int64? } } @@ -49,7 +52,8 @@ extension AppSettingsPayload.UploadConfigPayload { allowedFileExtensions: allowedFileExtensions, blockedFileExtensions: blockedFileExtensions, allowedMimeTypes: allowedMimeTypes, - blockedMimeTypes: blockedMimeTypes + blockedMimeTypes: blockedMimeTypes, + sizeLimitInBytes: sizeLimit ) } } diff --git a/Sources/StreamChatUI/Composer/ComposerVC.swift b/Sources/StreamChatUI/Composer/ComposerVC.swift index 314030758ee..38c937cef43 100644 --- a/Sources/StreamChatUI/Composer/ComposerVC.swift +++ b/Sources/StreamChatUI/Composer/ComposerVC.swift @@ -13,6 +13,8 @@ public enum AttachmentValidationError: Error { /// The number of attachments reached the limit. case maxAttachmentsCountPerMessageExceeded(limit: Int) + + internal static var fileSizeMaxLimitFallback: Int64 = 100 * 1024 * 1024 } public struct LocalAttachmentInfoKey: Hashable, Equatable, RawRepresentable { @@ -1201,7 +1203,8 @@ open class ComposerVC: _ViewController, } let fileSize = try AttachmentFile(url: url).size - guard fileSize < chatConfig.maxAttachmentSize else { + let maxAttachmentSize = maxAttachmentSize(for: type) + guard fileSize <= maxAttachmentSize else { throw AttachmentValidationError.maxFileSizeExceeded } @@ -1231,6 +1234,35 @@ open class ComposerVC: _ViewController, content.attachments.append(attachment) } + /// The maximum upload file size depending on the attachment type. + /// + /// The max attachment size can be set from the Stream's Dashboard App Settings. + /// If it is not set, it fallbacks to the deprecated `ChatClientConfig.maxAttachmentSize`. + /// - Parameter attachmentType: The attachment type that is being uploaded. + /// - Returns: The file size limit in bytes. The default value is 100MB. + open func maxAttachmentSize(for attachmentType: AttachmentType) -> Int64 { + guard let client = channelController?.client else { + log.assertionFailure("Channel controller must be set at this point") + return AttachmentValidationError.fileSizeMaxLimitFallback + } + + let maxAttachmentSize: Int64? + switch attachmentType { + case .image: + maxAttachmentSize = client.appSettings?.imageUploadConfig.sizeLimitInBytes + default: + maxAttachmentSize = client.appSettings?.fileUploadConfig.sizeLimitInBytes + } + + // If no value is set in the dashboard, the size_limit will be nil or zero, + // so in this case we fallback to the deprecated value. + guard let maxSize = maxAttachmentSize, maxSize > 0 else { + return client.config.maxAttachmentSize + } + + return maxSize + } + /// Shows an alert for the error thrown when adding attachment to a composer. /// - Parameters: /// - attachmentURL: The attachment's file URL. diff --git a/TestTools/StreamChatTestTools/Fixtures/JSONs/AppSettings.json b/TestTools/StreamChatTestTools/Fixtures/JSONs/AppSettings.json index 508ef91e081..7e2462807d2 100644 --- a/TestTools/StreamChatTestTools/Fixtures/JSONs/AppSettings.json +++ b/TestTools/StreamChatTestTools/Fixtures/JSONs/AppSettings.json @@ -13,7 +13,8 @@ ], "blocked_mime_types":[ "image/webp" - ] + ], + "size_limit": 104857600 }, "image_upload_config":{ "allowed_file_extensions":[ @@ -27,7 +28,8 @@ ], "blocked_mime_types":[ "audio/mp4" - ] + ], + "size_limit": 10485760 }, "video_provider":"", "auto_translation_enabled":true, diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/ChatClient_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/ChatClient_Mock.swift index 0bd645e1b04..6a18ef39bb7 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/ChatClient_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/ChatClient_Mock.swift @@ -21,6 +21,12 @@ final class ChatClient_Mock: ChatClient { @Atomic var completeTokenWaiters_called = false @Atomic var completeTokenWaiters_token: Token? + var mockedAppSettings: AppSettings? + + override var appSettings: AppSettings? { + mockedAppSettings + } + var mockedEventNotificationCenter: EventNotificationCenter_Mock? = nil override var eventNotificationCenter: EventNotificationCenter { @@ -256,3 +262,39 @@ extension ChatClient { ) } } + +extension AppSettings { + static func mock( + name: String = "Stream iOS", + fileUploadConfig: UploadConfig? = nil, + imageUploadConfig: UploadConfig? = nil, + autoTranslationEnabled: Bool = false, + asyncUrlEnrichEnabled: Bool = false + ) -> AppSettings { + .init( + name: name, + fileUploadConfig: fileUploadConfig ?? .mock(), + imageUploadConfig: imageUploadConfig ?? .mock(), + autoTranslationEnabled: autoTranslationEnabled, + asyncUrlEnrichEnabled: asyncUrlEnrichEnabled + ) + } +} + +extension AppSettings.UploadConfig { + static func mock( + allowedFileExtensions: [String] = [], + blockedFileExtensions: [String] = [], + allowedMimeTypes: [String] = [], + blockedMimeTypes: [String] = [], + sizeLimitInBytes: Int64? = nil + ) -> AppSettings.UploadConfig { + .init( + allowedFileExtensions: allowedFileExtensions, + blockedFileExtensions: blockedFileExtensions, + allowedMimeTypes: allowedMimeTypes, + blockedMimeTypes: blockedMimeTypes, + sizeLimitInBytes: sizeLimitInBytes + ) + } +} diff --git a/Tests/StreamChatTests/APIClient/Endpoints/Payloads/AppSettingsPayload_Tests.swift b/Tests/StreamChatTests/APIClient/Endpoints/Payloads/AppSettingsPayload_Tests.swift index b8894c0ab71..6475b794f2d 100644 --- a/Tests/StreamChatTests/APIClient/Endpoints/Payloads/AppSettingsPayload_Tests.swift +++ b/Tests/StreamChatTests/APIClient/Endpoints/Payloads/AppSettingsPayload_Tests.swift @@ -17,9 +17,11 @@ final class AppSettingsPayload_Tests: XCTestCase { XCTAssertEqual(payload.app.fileUploadConfig.blockedFileExtensions, ["webp"]) XCTAssertEqual(payload.app.fileUploadConfig.allowedMimeTypes, ["image/jpg", "image/png"]) XCTAssertEqual(payload.app.fileUploadConfig.blockedMimeTypes, ["image/webp"]) + XCTAssertEqual(payload.app.fileUploadConfig.sizeLimit, 104_857_600) XCTAssertEqual(payload.app.imageUploadConfig.allowedFileExtensions, ["mp3", "wav"]) XCTAssertEqual(payload.app.imageUploadConfig.blockedFileExtensions, ["mp4"]) XCTAssertEqual(payload.app.imageUploadConfig.allowedMimeTypes, ["audio/mp3", "audio/wav"]) XCTAssertEqual(payload.app.imageUploadConfig.blockedMimeTypes, ["audio/mp4"]) + XCTAssertEqual(payload.app.imageUploadConfig.sizeLimit, 10_485_760) } } diff --git a/Tests/StreamChatUITests/SnapshotTests/Composer/ComposerVC_Tests.swift b/Tests/StreamChatUITests/SnapshotTests/Composer/ComposerVC_Tests.swift index 964541b6a49..922ae86d733 100644 --- a/Tests/StreamChatUITests/SnapshotTests/Composer/ComposerVC_Tests.swift +++ b/Tests/StreamChatUITests/SnapshotTests/Composer/ComposerVC_Tests.swift @@ -967,6 +967,55 @@ final class ComposerVC_Tests: XCTestCase { XCTAssertEqual(composerVC.dismissLinkPreviewCallCount, 0) } + // MARK: - maxAttachmentSize + + func test_maxAttachmentSize_whenChannelControllerNotSet_thenReturnsDefaultFallbackLimit() { + composerVC.channelController = nil + XCTAssertEqual(composerVC.maxAttachmentSize(for: .file), AttachmentValidationError.fileSizeMaxLimitFallback) + } + + func test_maxAttachmentSize_whenImageType_thenReturnsLimitFromImageUploadConfig() { + let expectedValue: Int64 = 50 * 1024 * 1024 + let chatClient = ChatClient_Mock.mock + chatClient.mockedAppSettings = .mock(imageUploadConfig: .mock( + sizeLimitInBytes: expectedValue + )) + composerVC.channelController = ChatChannelController_Mock.mock(chatClient: chatClient) + + XCTAssertEqual(composerVC.maxAttachmentSize(for: .image), expectedValue) + } + + func test_maxAttachmentSize_whenFileType_thenReturnsLimitFromFileUploadConfig() { + let expectedValue: Int64 = 50 * 1024 * 1024 + let chatClient = ChatClient_Mock.mock + chatClient.mockedAppSettings = .mock(fileUploadConfig: .mock( + sizeLimitInBytes: expectedValue + )) + composerVC.channelController = ChatChannelController_Mock.mock(chatClient: chatClient) + + XCTAssertEqual(composerVC.maxAttachmentSize(for: .file), expectedValue) + } + + func test_maxAttachmentSize_whenOtherType_thenReturnsLimitFromFileUploadConfig() { + let expectedValue: Int64 = 50 * 1024 * 1024 + let chatClient = ChatClient_Mock.mock + chatClient.mockedAppSettings = .mock(fileUploadConfig: .mock( + sizeLimitInBytes: expectedValue + )) + composerVC.channelController = ChatChannelController_Mock.mock(chatClient: chatClient) + + XCTAssertEqual(composerVC.maxAttachmentSize(for: .video), expectedValue) + } + + func test_maxAttachmentSize_whenSizeLimitNotDefined_thenReturnsLimitFromChatClientConfig() { + let expectedValue: Int64 = 50 * 1024 * 1024 + var config = ChatClientConfig(apiKeyString: "sadsad") + config.maxAttachmentSize = expectedValue + composerVC.channelController = ChatChannelController_Mock.mock(chatClientConfig: config) + + XCTAssertEqual(composerVC.maxAttachmentSize(for: .image), expectedValue) + } + // MARK: - audioPlayer func test_audioPlayer_voiceRecordingAndAttachmentsVCGetTheSameInstance() {