New SDK components extracted from product apps: - BLBlobClient — Azure Blob Storage upload via SAS tokens (from LysnrAI BlobService) - BLKillSwitchClient — Kill switch check from platform-service (from LysnrAI KillSwitchService) - BLLicenseClient — License key activation + status (from LysnrAI LicenseService) - BLBiometricAuth — Face ID / Touch ID wrapper (from LysnrAI BiometricAuth) - BLCrashReporter — MetricKit crash reporting (from ChronoMind CrashReporter) - BLAuditLogger — Local rotating JSON audit log (from LysnrAI AuditLogger) SDK now has 13 source files. Updated README with full component table and migration status (3 apps fully migrated, 18 wrappers total).
87 lines
3.2 KiB
Swift
87 lines
3.2 KiB
Swift
// ── 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)
|
|
}
|
|
}
|