Skip to content

Commit

Permalink
Add support for defining file size limit per attachment type (#3105)
Browse files Browse the repository at this point in the history
  • Loading branch information
nuno-vieira committed Mar 25, 2024
1 parent d979556 commit 2ad5fcc
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 8 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ struct AppSettingsPayload: Decodable {
let blockedFileExtensions: [String]
let allowedMimeTypes: [String]
let blockedMimeTypes: [String]
let sizeLimit: Int64?
}
}

Expand Down Expand Up @@ -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"
}
}
1 change: 1 addition & 0 deletions Sources/StreamChat/Config/ChatClientConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
14 changes: 9 additions & 5 deletions Sources/StreamChat/Models/AppSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?
}
}

Expand All @@ -49,7 +52,8 @@ extension AppSettingsPayload.UploadConfigPayload {
allowedFileExtensions: allowedFileExtensions,
blockedFileExtensions: blockedFileExtensions,
allowedMimeTypes: allowedMimeTypes,
blockedMimeTypes: blockedMimeTypes
blockedMimeTypes: blockedMimeTypes,
sizeLimitInBytes: sizeLimit
)
}
}
34 changes: 33 additions & 1 deletion Sources/StreamChatUI/Composer/ComposerVC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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.
Expand Down
6 changes: 4 additions & 2 deletions TestTools/StreamChatTestTools/Fixtures/JSONs/AppSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
],
"blocked_mime_types":[
"image/webp"
]
],
"size_limit": 104857600
},
"image_upload_config":{
"allowed_file_extensions":[
Expand All @@ -27,7 +28,8 @@
],
"blocked_mime_types":[
"audio/mp4"
]
],
"size_limit": 10485760
},
"video_provider":"",
"auto_translation_enabled":true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down

0 comments on commit 2ad5fcc

Please sign in to comment.