From d1b4534b22ed7659622ffc030e938645e973ebbb Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Sat, 28 Feb 2026 22:12:37 -0800 Subject: [PATCH] =?UTF-8?q?refactor(ios):=20migrate=20Cloud/=20files=20to?= =?UTF-8?q?=20ByteLystPlatformSDK=20=E2=80=94=20eliminate=20duplicated=20p?= =?UTF-8?q?latform=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced standalone implementations with thin wrappers over shared SDK: - KeychainHelper → delegates to BLKeychain (53→27 lines) - TelemetryService → delegates to BLTelemetryClient (139→55 lines) - FeatureFlagService → delegates to BLFeatureFlagClient (72→39 lines) - AuthService → delegates to BLAuthClient (359→171 lines) - project.yml → added ByteLystPlatformSDK local package dependency Total: 623 lines of duplicated code → 292 lines of thin wrappers. PlatformSyncManager kept as-is (product-specific timer DTOs). All existing call-site APIs preserved — zero breaking changes. --- ios/ChronoMind/Shared/Cloud/AuthService.swift | 314 ++++-------------- .../Shared/Cloud/FeatureFlagService.swift | 63 +--- .../Shared/Cloud/KeychainHelper.swift | 40 +-- .../Shared/Cloud/TelemetryService.swift | 124 ++----- ios/project.yml | 7 + 5 files changed, 112 insertions(+), 436 deletions(-) diff --git a/ios/ChronoMind/Shared/Cloud/AuthService.swift b/ios/ChronoMind/Shared/Cloud/AuthService.swift index 2caa06e..718081a 100644 --- a/ios/ChronoMind/Shared/Cloud/AuthService.swift +++ b/ios/ChronoMind/Shared/Cloud/AuthService.swift @@ -1,50 +1,13 @@ // ── Auth Service ────────────────────────────────────────────── -// Login, register, refresh, logout via platform-service /auth/* endpoints. -// Stores tokens in Keychain; wires into PlatformSyncManager. +// Thin wrapper over ByteLystPlatformSDK's BLAuthClient. +// Keeps existing call-site API + ChronoMind-specific sync wiring. +// AuthUser = BLAuthUser (re-exported from SDK). import Foundation import SwiftUI +import ByteLystPlatformSDK -struct AuthUser: Codable { - let id: String - let email: String - let name: String - let plan: String - let role: String - - enum CodingKeys: String, CodingKey { - case id, email, plan, role - case name = "displayName" - } - - init(id: String, email: String, name: String, plan: String, role: String = "user") { - self.id = id - self.email = email - self.name = name - self.plan = plan - self.role = role - } - - init(from decoder: Decoder) throws { - let c = try decoder.container(keyedBy: CodingKeys.self) - id = try c.decode(String.self, forKey: .id) - email = try c.decode(String.self, forKey: .email) - name = try c.decode(String.self, forKey: .name) - plan = try c.decodeIfPresent(String.self, forKey: .plan) ?? "free" - role = try c.decodeIfPresent(String.self, forKey: .role) ?? "user" - } -} - -private struct TokenResponse: Codable { - let accessToken: String - let refreshToken: String - let user: AuthUser -} - -private struct RefreshResponse: Codable { - let accessToken: String - let refreshToken: String -} +typealias AuthUser = BLAuthUser enum CMAuthState { case loading @@ -62,42 +25,35 @@ final class CMAuthService: ObservableObject { @AppStorage("cm_user_name") private var userName = "" @AppStorage("cm_user_plan") private var userPlan = "free" - private var accessToken: String { - get { KeychainHelper.read(key: "cm_access_token") ?? "" } - set { - if newValue.isEmpty { KeychainHelper.delete(key: "cm_access_token") } - else { KeychainHelper.save(key: "cm_access_token", value: newValue) } - } - } - - private var refreshToken: String { - get { KeychainHelper.read(key: "cm_refresh_token") ?? "" } - set { - if newValue.isEmpty { KeychainHelper.delete(key: "cm_refresh_token") } - else { KeychainHelper.save(key: "cm_refresh_token", value: newValue) } - } - } - - private var refreshTimer: Timer? - - private var baseURL: String { - Bundle.main.object(forInfoDictionaryKey: "PLATFORM_SERVICE_URL") as? String - ?? "https://api.chronomind.app" - } - - private let productId = "chronomind" + private let authClient: BLAuthClient + private let platformClient: BLPlatformClient private init() { + let config = BLPlatformConfig.fromInfoPlist( + productId: "chronomind", + defaultBaseURL: "https://api.chronomind.app", + bundleId: "com.saravana.chronomind", + appGroupId: "group.com.chronomind.shared" + ) + platformClient = BLPlatformClient(config: config) + authClient = BLAuthClient(config: config, client: platformClient) + + // Wire token updates → sync manager + authClient.onTokensUpdated = { [weak self] token in + Task { @MainActor in + self?.wireSyncToken(token) + } + } + checkExistingSession() } private func checkExistingSession() { - if !accessToken.isEmpty, !userEmail.isEmpty { - state = .loggedIn(AuthUser(id: "", email: userEmail, name: userName, plan: userPlan)) - wireSyncToken() - startRefreshTimer() + if authClient.isAuthenticated, !userEmail.isEmpty { + state = .loggedIn(AuthUser(id: "", email: userEmail, displayName: userName, plan: userPlan)) + wireSyncToken(authClient.accessToken) Task { await fetchCurrentUser() } - } else if !accessToken.isEmpty { + } else if authClient.isAuthenticated { Task { await fetchCurrentUser() } } else { state = .loggedOut @@ -108,62 +64,36 @@ final class CMAuthService: ObservableObject { func login(email: String, password: String) async { state = .loading - let body: [String: String] = [ - "email": email, - "password": password, - "productId": productId, - ] - guard let result = await postAuth(path: "/auth/login", body: body) else { + do { + let user = try await authClient.login(email: email, password: password) + saveUserInfo(user) + state = .loggedIn(user) + } catch { state = .error("Invalid email or password") - return } - saveSession(result) } func register(name: String, email: String, password: String) async { state = .loading - let body: [String: String] = [ - "email": email, - "displayName": name, - "password": password, - "productId": productId, - ] - guard let result = await postAuth(path: "/auth/register", body: body) else { + do { + let user = try await authClient.register(displayName: name, email: email, password: password) + saveUserInfo(user) + state = .loggedIn(user) + } catch { state = .error("Registration failed") - return } - saveSession(result) } func logout() { - stopRefreshTimer() - accessToken = "" - refreshToken = "" + authClient.logout() userEmail = "" userName = "" state = .loggedOut - PlatformSyncManager.shared.setAuthToken(nil) } func forgotPassword(email: String) async -> String? { - guard let url = URL(string: "\(baseURL)/auth/forgot-password"), - let jsonData = try? JSONSerialization.data(withJSONObject: [ - "email": email, "productId": productId - ]) else { return "Invalid request" } - - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.setValue(productId, forHTTPHeaderField: "X-Product-Id") - request.setValue(UUID().uuidString, forHTTPHeaderField: "X-Request-Id") - request.httpBody = jsonData - request.timeoutInterval = 15 - do { - let (_, response) = try await URLSession.shared.data(for: request) - guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { - return "Failed to send reset email" - } + try await authClient.forgotPassword(email: email) return nil } catch { return error.localizedDescription @@ -171,28 +101,8 @@ final class CMAuthService: ObservableObject { } func changePassword(currentPassword: String, newPassword: String) async -> String? { - guard !accessToken.isEmpty, - let url = URL(string: "\(baseURL)/auth/change-password"), - let jsonData = try? JSONSerialization.data(withJSONObject: [ - "currentPassword": currentPassword, "newPassword": newPassword - ]) else { return "Not authenticated" } - - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") - request.setValue(productId, forHTTPHeaderField: "X-Product-Id") - request.setValue(UUID().uuidString, forHTTPHeaderField: "X-Request-Id") - request.httpBody = jsonData - request.timeoutInterval = 15 - do { - let (data, response) = try await URLSession.shared.data(for: request) - guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { - if let body = try? JSONSerialization.jsonObject(with: data) as? [String: Any], - let msg = body["message"] as? String { return msg } - return "Failed to change password" - } + try await authClient.changePassword(currentPassword: currentPassword, newPassword: newPassword) return nil } catch { return error.localizedDescription @@ -200,29 +110,11 @@ final class CMAuthService: ObservableObject { } func deleteAccount(password: String) async -> String? { - guard !accessToken.isEmpty, - let url = URL(string: "\(baseURL)/auth/account"), - let jsonData = try? JSONSerialization.data(withJSONObject: [ - "password": password - ]) else { return "Not authenticated" } - - var request = URLRequest(url: url) - request.httpMethod = "DELETE" - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") - request.setValue(productId, forHTTPHeaderField: "X-Product-Id") - request.setValue(UUID().uuidString, forHTTPHeaderField: "X-Request-Id") - request.httpBody = jsonData - request.timeoutInterval = 15 - do { - let (data, response) = try await URLSession.shared.data(for: request) - guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { - if let body = try? JSONSerialization.jsonObject(with: data) as? [String: Any], - let msg = body["message"] as? String { return msg } - return "Failed to delete account" - } - logout() + try await authClient.deleteAccount(password: password) + userEmail = "" + userName = "" + state = .loggedOut return nil } catch { return error.localizedDescription @@ -240,119 +132,39 @@ final class CMAuthService: ObservableObject { } func getAccessToken() -> String? { - let t = accessToken - return t.isEmpty ? nil : t + authClient.accessToken } // MARK: - Token Refresh func refreshAccessToken() async -> Bool { - guard !refreshToken.isEmpty else { return false } - let body: [String: String] = ["refreshToken": refreshToken] - guard let url = URL(string: "\(baseURL)/auth/refresh"), - let jsonData = try? JSONSerialization.data(withJSONObject: body) else { return false } - - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.setValue(productId, forHTTPHeaderField: "X-Product-Id") - request.httpBody = jsonData - request.timeoutInterval = 10 + let ok = await authClient.refreshAccessToken() + if !ok { logout() } + return ok + } + func fetchCurrentUser() async { do { - let (data, response) = try await URLSession.shared.data(for: request) - guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { - if let http = response as? HTTPURLResponse, http.statusCode == 401 { logout() } - return false - } - let r = try JSONDecoder().decode(RefreshResponse.self, from: data) - accessToken = r.accessToken - refreshToken = r.refreshToken - wireSyncToken() - return true + let user = try await authClient.getMe() + saveUserInfo(user) + state = .loggedIn(user) } catch { - return false + if let netErr = error as? BLNetworkError, netErr.statusCode == 401 { + let ok = await refreshAccessToken() + if ok { await fetchCurrentUser() } else { state = .loggedOut } + } } } - private func startRefreshTimer() { - stopRefreshTimer() - refreshTimer = Timer.scheduledTimer(withTimeInterval: 45 * 60, repeats: true) { [weak self] _ in - guard let self else { return } - Task { @MainActor in _ = await self.refreshAccessToken() } - } - } - - private func stopRefreshTimer() { - refreshTimer?.invalidate() - refreshTimer = nil - } - // MARK: - Helpers - private func wireSyncToken() { - PlatformSyncManager.shared.setAuthToken(accessToken.isEmpty ? nil : accessToken) + private func wireSyncToken(_ token: String?) { + PlatformSyncManager.shared.setAuthToken(token) } - private func postAuth(path: String, body: [String: String]) async -> TokenResponse? { - guard let url = URL(string: "\(baseURL)\(path)"), - let jsonData = try? JSONSerialization.data(withJSONObject: body) else { return nil } - - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.setValue(productId, forHTTPHeaderField: "X-Product-Id") - request.setValue(UUID().uuidString, forHTTPHeaderField: "X-Request-Id") - request.httpBody = jsonData - request.timeoutInterval = 15 - - do { - let (data, response) = try await URLSession.shared.data(for: request) - guard let http = response as? HTTPURLResponse, (200...201).contains(http.statusCode) else { return nil } - return try JSONDecoder().decode(TokenResponse.self, from: data) - } catch { - return nil - } - } - - private func saveSession(_ resp: TokenResponse) { - accessToken = resp.accessToken - refreshToken = resp.refreshToken - userEmail = resp.user.email - userName = resp.user.name - userPlan = resp.user.plan - state = .loggedIn(resp.user) - wireSyncToken() - startRefreshTimer() - } - - func fetchCurrentUser() async { - guard !accessToken.isEmpty, let url = URL(string: "\(baseURL)/auth/me") else { return } - var request = URLRequest(url: url) - request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") - request.setValue(productId, forHTTPHeaderField: "X-Product-Id") - request.timeoutInterval = 10 - - do { - let (data, response) = try await URLSession.shared.data(for: request) - guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { - if let http = response as? HTTPURLResponse, http.statusCode == 401 { - let ok = await refreshAccessToken() - if ok { await fetchCurrentUser() } else { state = .loggedOut } - } - return - } - struct MeResponse: Codable { - let id: String; let email: String; let displayName: String - let plan: String?; let role: String? - } - let info = try JSONDecoder().decode(MeResponse.self, from: data) - let plan = info.plan ?? "free" - userPlan = plan; userEmail = info.email; userName = info.displayName - state = .loggedIn(AuthUser(id: info.id, email: info.email, name: info.displayName, plan: plan, role: info.role ?? "user")) - wireSyncToken() - } catch { - // Keep existing state - } + private func saveUserInfo(_ user: AuthUser) { + userEmail = user.email + userName = user.displayName + userPlan = user.plan } } diff --git a/ios/ChronoMind/Shared/Cloud/FeatureFlagService.swift b/ios/ChronoMind/Shared/Cloud/FeatureFlagService.swift index 882c877..776864a 100644 --- a/ios/ChronoMind/Shared/Cloud/FeatureFlagService.swift +++ b/ios/ChronoMind/Shared/Cloud/FeatureFlagService.swift @@ -1,71 +1,38 @@ // ── Feature Flag Client ─────────────────────────────────────── -// Polls platform-service /flags/poll for feature flags. -// Flags cached in memory, re-polled every 5 minutes. -// Consumers call FeatureFlagService.shared.isEnabled("flag_key"). +// Thin wrapper over ByteLystPlatformSDK's BLFeatureFlagClient. +// Keeps existing call-site API (FeatureFlagService.shared.isEnabled). import Foundation +import ByteLystPlatformSDK @MainActor final class FeatureFlagService: ObservableObject { static let shared = FeatureFlagService() @Published private(set) var flags: [String: Bool] = [:] - private var pollTimer: Timer? - private let pollIntervalSec: TimeInterval = 5 * 60 // 5 minutes - private let productId = "chronomind" + private let flagClient: BLFeatureFlagClient - private var baseURL: String { - Bundle.main.object(forInfoDictionaryKey: "PLATFORM_SERVICE_URL") as? String - ?? "https://api.chronomind.app" + private init() { + let config = BLPlatformConfig.fromInfoPlist( + productId: "chronomind", + defaultBaseURL: "https://api.chronomind.app", + bundleId: "com.saravana.chronomind" + ) + let client = BLPlatformClient(config: config) + flagClient = BLFeatureFlagClient(config: config, client: client) } - private init() {} - // MARK: - Public API func start(userId: String? = nil) { - Task { await fetchFlags(userId: userId) } - pollTimer?.invalidate() - pollTimer = Timer.scheduledTimer(withTimeInterval: pollIntervalSec, repeats: true) { [weak self] _ in - guard let self else { return } - Task { @MainActor in await self.fetchFlags(userId: userId) } - } + flagClient.start(userId: userId) } func stop() { - pollTimer?.invalidate() - pollTimer = nil + flagClient.stop() } func isEnabled(_ key: String) -> Bool { - flags[key] == true - } - - // MARK: - Fetch - - private func fetchFlags(userId: String? = nil) async { - var components = URLComponents(string: "\(baseURL)/api/flags/poll") - var queryItems: [URLQueryItem] = [.init(name: "platform", value: "ios")] - if let userId { queryItems.append(.init(name: "userId", value: userId)) } - components?.queryItems = queryItems - - guard let url = components?.url else { return } - - var request = URLRequest(url: url) - request.setValue(productId, forHTTPHeaderField: "X-Product-Id") - request.timeoutInterval = 10 - - do { - let (data, response) = try await URLSession.shared.data(for: request) - guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { return } - - struct FlagsResponse: Codable { - let flags: [String: Bool] - } - let parsed = try JSONDecoder().decode(FlagsResponse.self, from: data) - flags = parsed.flags - } catch { - // Keep existing flags on error - } + flagClient.isEnabled(key) } } diff --git a/ios/ChronoMind/Shared/Cloud/KeychainHelper.swift b/ios/ChronoMind/Shared/Cloud/KeychainHelper.swift index f11a859..1307fcb 100644 --- a/ios/ChronoMind/Shared/Cloud/KeychainHelper.swift +++ b/ios/ChronoMind/Shared/Cloud/KeychainHelper.swift @@ -1,8 +1,10 @@ // ── Keychain Helper ─────────────────────────────────────────── -// Lightweight wrapper for storing auth tokens securely in iOS Keychain. +// Thin wrapper over ByteLystPlatformSDK's BLKeychain. +// Keeps the existing call-site API (KeychainHelper.save/read/delete) +// while delegating to the shared implementation. import Foundation -import Security +import ByteLystPlatformSDK enum KeychainHelper { @@ -10,43 +12,15 @@ enum KeychainHelper { @discardableResult static func save(key: String, value: String) -> Bool { - guard let data = value.data(using: .utf8) else { return false } - delete(key: key) - - let query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrService as String: service, - kSecAttrAccount as String: key, - kSecValueData as String: data, - kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock, - ] - - return SecItemAdd(query as CFDictionary, nil) == errSecSuccess + BLKeychain.save(service: service, key: key, value: value) } static func read(key: String) -> String? { - let query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrService as String: service, - kSecAttrAccount as String: key, - kSecReturnData as String: true, - kSecMatchLimit as String: kSecMatchLimitOne, - ] - - var result: AnyObject? - let status = SecItemCopyMatching(query as CFDictionary, &result) - guard status == errSecSuccess, let data = result as? Data else { return nil } - return String(data: data, encoding: .utf8) + BLKeychain.read(service: service, key: key) } @discardableResult static func delete(key: String) -> Bool { - let query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrService as String: service, - kSecAttrAccount as String: key, - ] - let status = SecItemDelete(query as CFDictionary) - return status == errSecSuccess || status == errSecItemNotFound + BLKeychain.delete(service: service, key: key) } } diff --git a/ios/ChronoMind/Shared/Cloud/TelemetryService.swift b/ios/ChronoMind/Shared/Cloud/TelemetryService.swift index 3c113ba..1c5534e 100644 --- a/ios/ChronoMind/Shared/Cloud/TelemetryService.swift +++ b/ios/ChronoMind/Shared/Cloud/TelemetryService.swift @@ -1,76 +1,32 @@ // ── Platform Telemetry Client ───────────────────────────────── -// Sends events to platform-service /telemetry/events endpoint. +// Thin wrapper over ByteLystPlatformSDK's BLTelemetryClient. +// Keeps existing call-site API (CMTelemetryService.shared.trackEvent/trackTimer/trackScreen). // Privacy: no PII, only action names + timing metrics. -// Fire-and-forget — errors never surface to the user. import Foundation +import ByteLystPlatformSDK @MainActor final class CMTelemetryService { static let shared = CMTelemetryService() - private let productId = "chronomind" - private let platform = "ios" - private let channel = "native" - private var queue: [TelemetryPayload] = [] - private var flushTimer: Timer? - private let maxQueue = 50 - private let flushIntervalSec: TimeInterval = 30 + private let telemetry: BLTelemetryClient - private var installId: String { - let key = "chronomind-telemetry-install-id" - if let id = UserDefaults.standard.string(forKey: key) { return id } - let id = UUID().uuidString - UserDefaults.standard.set(id, forKey: key) - return id + private init() { + let config = BLPlatformConfig.fromInfoPlist( + productId: "chronomind", + defaultBaseURL: "https://api.chronomind.app", + bundleId: "com.saravana.chronomind", + appGroupId: "group.com.chronomind.shared" + ) + let client = BLPlatformClient(config: config) + telemetry = BLTelemetryClient(config: config, client: client) } - private let sessionId = UUID().uuidString - - private var baseURL: String { - Bundle.main.object(forInfoDictionaryKey: "PLATFORM_SERVICE_URL") as? String - ?? "https://api.chronomind.app" - } - - private struct TelemetryPayload: Codable { - let id: String - let productId: String - let anonymousInstallId: String - let sessionId: String - let platform: String - let channel: String - let osFamily: String - let osVersion: String - let appVersion: String - let buildNumber: String - let releaseChannel: String - let eventType: String - let module: String - let eventName: String - let feature: String? - let message: String? - let tags: [String: String]? - let metrics: [String: Double]? - let occurredAt: String - } - - private init() {} - // MARK: - Public API - func start() { - guard flushTimer == nil else { return } - flushTimer = Timer.scheduledTimer(withTimeInterval: flushIntervalSec, repeats: true) { [weak self] _ in - guard let self else { return } - Task { @MainActor in self.flush() } - } - } - - func stop() { - flush() - flushTimer?.invalidate() - flushTimer = nil - } + func start() { telemetry.start() } + func stop() { telemetry.stop() } func trackEvent( _ eventType: String, @@ -81,32 +37,9 @@ final class CMTelemetryService { tags: [String: String]? = nil, metrics: [String: Double]? = nil ) { - let event = TelemetryPayload( - id: UUID().uuidString, - productId: productId, - anonymousInstallId: installId, - sessionId: sessionId, - platform: platform, - channel: channel, - osFamily: "ios", - osVersion: ProcessInfo.processInfo.operatingSystemVersionString, - appVersion: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.1.0", - buildNumber: Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "1", - releaseChannel: "beta", - eventType: eventType, - module: module, - eventName: name, - feature: feature, - message: message, - tags: tags, - metrics: metrics, - occurredAt: ISO8601DateFormatter().string(from: Date()) - ) - queue.append(event) - - if queue.count >= maxQueue { - flush() - } + telemetry.trackEvent(eventType, module: module, name: name, + feature: feature, message: message, + tags: tags, metrics: metrics) } func trackTimer(_ name: String, tags: [String: String]? = nil, metrics: [String: Double]? = nil) { @@ -114,25 +47,8 @@ final class CMTelemetryService { } func trackScreen(_ screen: String) { - trackEvent("info", module: "navigation", name: "screen_view", tags: ["screen": screen]) + telemetry.trackScreen(screen) } - func flush() { - guard !queue.isEmpty else { return } - let batch = queue - queue.removeAll() - - Task.detached { [baseURL, productId] in - guard let url = URL(string: "\(baseURL)/telemetry/events") else { return } - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.setValue(productId, forHTTPHeaderField: "X-Product-Id") - request.setValue(UUID().uuidString, forHTTPHeaderField: "X-Request-Id") - request.httpBody = try? JSONEncoder().encode(["events": batch]) - request.timeoutInterval = 10 - - _ = try? await URLSession.shared.data(for: request) - } - } + func flush() { telemetry.flush() } } diff --git a/ios/project.yml b/ios/project.yml index ed83d8a..3ab4d03 100644 --- a/ios/project.yml +++ b/ios/project.yml @@ -10,6 +10,10 @@ options: generateEmptyDirectories: true groupSortPosition: top +packages: + ByteLystPlatformSDK: + path: ../../learning_ai_common_plat/packages/swift-platform-sdk + settings: base: SWIFT_VERSION: "5.9" @@ -45,6 +49,7 @@ targets: dependencies: - target: ChronoMindWidgets - target: ChronoMindWatch + - package: ByteLystPlatformSDK entitlements: path: ChronoMind/ChronoMind.entitlements properties: @@ -159,6 +164,8 @@ targets: - path: ChronoMind/Shared/Reschedule - path: ChronoMind/Shared/Gamification - path: ChronoMind/Shared/Cloud + dependencies: + - package: ByteLystPlatformSDK settings: base: PRODUCT_BUNDLE_IDENTIFIER: com.chronomind.mac