// ── Blob Storage Client ───────────────────────────────────── // Generic Azure Blob Storage client via platform-service SAS tokens. // Upload files to Azure Blob using SAS URL from POST /api/blob/sas. // Product apps configure with BLPlatformConfig. import Foundation /// Response from platform-service SAS token endpoint. public struct BLSASResponse: Codable, Sendable { public let sasUrl: String public let blobUrl: String public let container: String public let blobName: String } /// Generic blob storage client for all ByteLyst iOS apps. /// Handles SAS token acquisition + direct Azure Blob upload. public final class BLBlobClient { private let config: BLPlatformConfig private let client: BLPlatformClient public init(config: BLPlatformConfig, client: BLPlatformClient) { self.config = config self.client = client } // MARK: - Upload /// Upload data to Azure Blob Storage. /// 1. Acquires SAS token from platform-service /// 2. Uploads directly to Azure Blob using the SAS URL /// Returns the permanent blob URL on success. public func upload( data: Data, container: String, fileName: String, contentType: String ) async throws -> String { // Step 1: Get SAS token let sas = try await getSASToken(container: container, blobName: fileName, permissions: "w") // Step 2: Upload to blob storage using SAS URL guard let url = URL(string: sas.sasUrl) else { throw BLNetworkError.invalidURL(sas.sasUrl) } var request = URLRequest(url: url) request.httpMethod = "PUT" request.setValue(contentType, forHTTPHeaderField: "Content-Type") request.setValue("BlockBlob", forHTTPHeaderField: "x-ms-blob-type") request.httpBody = data request.timeoutInterval = 120 let (_, response) = try await URLSession.shared.data(for: request) guard let http = response as? HTTPURLResponse, (200...299).contains(http.statusCode) else { throw BLNetworkError.httpError(statusCode: (response as? HTTPURLResponse)?.statusCode ?? 0, message: "Blob upload failed") } return sas.blobUrl } /// Convenience: upload audio data. public func uploadAudio(data: Data, fileName: String) async throws -> String { try await upload(data: data, container: "audio", fileName: fileName, contentType: "audio/wav") } /// Convenience: upload an attachment (image, document, etc.). public func uploadAttachment(data: Data, fileName: String, contentType: String) async throws -> String { try await upload(data: data, container: "attachments", fileName: fileName, contentType: contentType) } // MARK: - SAS Token /// Get a SAS token from platform-service. public func getSASToken(container: String, blobName: String, permissions: String = "r") async throws -> BLSASResponse { let body = [ "container": container, "blobName": blobName, "permissions": permissions, ] return try await client.request(path: "/api/blob/sas", method: "POST", body: body, responseType: BLSASResponse.self) } }