// Example of your Upload API
protocol UploadFileAPI {
func uploadFile(data: Data, progress: ((Double) -> Void)?, completion: (@escaping (FileDetails) -> Void))
}
protocol FileDetails {
var url: URL { get }
var thumbnailURL: URL { get }
}
final class CustomCDNClient: CDNClient {
/// Example, max 100 MB. Required by CDNClient protocol.
static var maxAttachmentSize: Int64 { 100 * 1024 * 1024 }
let uploadFileApi: UploadFileAPI
init(uploadFileApi: UploadFileAPI) {
self.uploadFileApi = uploadFileApi
}
func uploadAttachment(
_ attachment: AnyChatMessageAttachment,
progress: ((Double) -> Void)?,
completion: @escaping (Result<UploadedFile, Error>
) -> Void) {
// The local file url is present in attachment.uploadingState.
guard let uploadingState = attachment.uploadingState,
let fileData = try? Data(contentsOf: uploadingState.localFileURL) else {
return completion(.failure(ClientError("Failed to upload attachment with id: \(attachment.id)")))
}
uploadFileApi.uploadFile(data: fileData, progress: progress) { file in
let uploadedFile = UploadedFile(url: file.url, thumbnailURL: file.thumbnailURL)
completion(.success(uploadedFile))
}
}
}
Custom CDN
By default, files are uploaded to Stream’s CDN, but you can also use your own CDN. Currently, there are two options to provide your custom file uploading logic.
Providing a
CDNClient
implementation. This is the simplest one and it is useful if you are only interested in changing the CDN URL and do not want to update the attachment payload.Providing an
AttachmentUploader
implementation. This one can be used for more fine-grain control, since you can change not only the URL but the attachment payload as well.
You should only pick 1 of the 2 options provided. Since using an AttachmentUploader
will override the custom CDNClient
implementation.
Custom CDNClient
implementation
In case you simply want to change the URL, here is an example of a custom CDNClient
implementation.
Then, you can set your custom implementation in the ChatClientConfig
:
var config = ChatClientConfig(apiKeyString: apiKeyString)
config.customCDNClient = CustomCDNClient(uploadFileApi: YourUploadFileApi())
In case your API does not support progress updates, you can simple ignore the argument.
Custom Firebase CDN
You can also use FirebaseStorage
as your custom CDN. There is a Swift Package called StreamFirebaseCDN
, built by the community to facilitate this, which can be found here.
Custom AttachmentUploader implementation
If you require changing more details of the attachment, in case your upload file API supports more features, you can provide a custom AttachmentUploader
.
// Example of your Upload API
protocol UploadFileAPI {
func uploadFile(data: Data, progress: ((Double) -> Void)?, completion: (@escaping (FileDetails) -> Void))
}
protocol FileDetails {
var name: String { get set }
var url: URL { get }
var thumbnailURL: URL { get set }
var codec: String { get set }
}
final class CustomUploader: AttachmentUploader {
private let attachmentUpdater = AnyAttachmentUpdater()
let uploadFileApi: UploadFileAPI
init(uploadFileApi: UploadFileAPI) {
self.uploadFileApi = uploadFileApi
}
func upload(
_ attachment: AnyChatMessageAttachment,
progress: ((Double) -> Void)?,
completion: @escaping (Result<UploadedAttachment, Error>) -> Void
) {
// The local file url is present in attachment.uploadingState.
guard let uploadingState = attachment.uploadingState,
let fileData = try? Data(contentsOf: uploadingState.localFileURL) else {
return completion(.failure(ClientError("Failed to upload attachment with id: \(attachment.id)")))
}
uploadFileApi.uploadFile(data: fileData, progress: progress) { [weak self] file in
var uploadedAttachment = UploadedAttachment(
attachment: attachment,
remoteURL: file.url
)
// Update the image payload, in case the attachment is an image.
self?.attachmentUpdater.update(
&uploadedAttachment.attachment,
forPayload: ImageAttachmentPayload.self
) { payload in
payload.title = file.name
payload.extraData = [
"thumbnailUrl": .string(file.thumbnailURL)
]
}
// Update the audio payload, in case the attachment is a audio.
self?.attachmentUpdater.update(
&uploadedAttachment.attachment,
forPayload: AudioAttachmentPayload.self
) { payload in
payload.title = file.name
payload.extraData = [
"thumbnailUrl": .string(file.thumbnailURL),
"codec": .string(file.codec)
]
}
completion(.success(uploadedAttachment))
}
}
}
The AnyAttachmentUpdater
is a helper component provided by Stream, to make it easier to update the underlying payload of a type-erased attachment. You should pass a reference of the attachment with &
and say which payload to update depending on what type is the attachment.
Finally, you should set your custom implementation in the ChatClientConfig
:
var config = ChatClientConfig(apiKeyString: apiKeyString)
config.customAttachmentUploader = CustomUploader(uploadFileApi: YourUploadFileApi())