Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Application/DevLogApp/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ let project = Project(
sourcePath: "Sources",
configPath: "Sources/.swiftlint.yml"
),
DevLogScripts.firebaseCrashlyticsDSYMUpload(),
],
dependencies: [
.project(target: "DevLogPresentation", path: "../DevLogPresentation"),
Expand All @@ -46,6 +47,7 @@ let project = Project(
base: [
"ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon",
"CODE_SIGN_STYLE": "Automatic",
"DEBUG_INFORMATION_FORMAT": "dwarf-with-dsym",
Comment thread
opficdev marked this conversation as resolved.
Outdated
"ENABLE_USER_SCRIPT_SANDBOXING": "NO",
"PRODUCT_MODULE_NAME": "DevLogApp",
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// FirebaseCrashlyticsHelper.swift
// DevLogInfra
//
// Created by opfic on 6/16/26.
//

import FirebaseCrashlytics
import Foundation

enum FirebaseCrashlyticsHelper {
static func record(
_ error: Error,
domain: String,
code: Int,
metadata: [String: String] = [:],
logs: [String] = []
) {
let nsError = error as NSError
let report = NSError(
domain: domain,
code: code,
userInfo: userInfo(for: nsError, error: error, metadata: metadata)
)
let crashlytics = Crashlytics.crashlytics()

logs.forEach {
crashlytics.log($0)
}

crashlytics.record(error: report)
}
}

private extension FirebaseCrashlyticsHelper {
enum Key: String {
case underlyingType
case underlyingDomain
case underlyingCode
}

static func userInfo(
for nsError: NSError,
error: Error,
metadata: [String: String]
) -> [String: Any] {
var userInfo: [String: Any] = [
Key.underlyingType.rawValue: String(describing: type(of: error)),
Key.underlyingDomain.rawValue: nsError.domain,
Key.underlyingCode.rawValue: nsError.code
]
Comment thread
opficdev marked this conversation as resolved.

metadata.forEach {
userInfo[$0.key] = $0.value
}

return userInfo
}
}
27 changes: 27 additions & 0 deletions Application/DevLogInfra/Sources/Service/AuthServiceImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ import DevLogCore
import DevLogData

final class AuthServiceImpl: AuthService {
private enum CrashlyticsError {
static let domain = "DevLogInfra.AuthServiceImpl"

enum Code: Int {
case getProviderID = 1
case deleteCurrentUser
case deleteMessagingToken
case signOut
}
}

private let store = Firestore.firestore()
private let messaging = Messaging.messaging()
private let logger = Logger(category: "AuthServiceImpl")
Expand Down Expand Up @@ -87,6 +98,7 @@ final class AuthServiceImpl: AuthService {
return providerID
} catch {
logger.error("Failed to fetch provider ID", error: error)
record(error, code: .getProviderID)
throw error
}
}
Expand All @@ -103,6 +115,7 @@ final class AuthServiceImpl: AuthService {
try await currentUser.delete()
} catch {
logger.error("Failed to delete FirebaseAuth current user", error: error)
record(error, code: .deleteCurrentUser)
throw error
}
}
Expand All @@ -114,19 +127,33 @@ final class AuthServiceImpl: AuthService {
try await messaging.deleteToken()
} catch {
logger.error("Failed to delete FCM token while clearing session", error: error)
record(error, code: .deleteMessagingToken)
}

do {
try Auth.auth().signOut()
} catch {
logger.error("Failed to sign out while clearing session", error: error)
record(error, code: .signOut)
throw error
}
}

}

private extension AuthServiceImpl {
private static func record(_ error: Error, code: CrashlyticsError.Code) {
FirebaseCrashlyticsHelper.record(
error,
domain: "\(CrashlyticsError.domain).\(code)",
code: code.rawValue
)
}

private func record(_ error: Error, code: CrashlyticsError.Code) {
Self.record(error, code: code)
}

func handleAuthStateChange(_ user: User?) {
let signedIn = user != nil
logger.info("Firebase auth state changed. signedIn: \(signedIn)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import DevLogData
import FirebaseCrashlytics
import FirebaseCore

final class FirebaseAppServiceImpl: FirebaseAppService {
Expand All @@ -15,6 +16,15 @@ final class FirebaseAppServiceImpl: FirebaseAppService {
guard !Self.isConfigured else { return }

FirebaseApp.configure()
configureCrashlyticsCollection()
Self.isConfigured = true
}
}

private extension FirebaseAppServiceImpl {
func configureCrashlyticsCollection() {
#if DEBUG
Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(false)
#endif
}
Comment thread
opficdev marked this conversation as resolved.
Outdated
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,33 @@ import Nexa
import DevLogData

final class ProfileImageDataServiceImpl: ProfileImageDataService {
private enum CrashlyticsError {
static let domain = "DevLogInfra.ProfileImageDataServiceImpl"

enum Code: Int {
case fetchImageData = 1
}
}

func fetchImageData(from url: URL) async throws -> Data {
try await NXAPIClient(
configuration: NXClientConfiguration(baseURL: url)
)
.get()
.timeout(10)
.intercept(ProfileImageDataCachePolicyInterceptor())
.validate(.successStatusCode)
.raw()
.data
do {
return try await NXAPIClient(
configuration: NXClientConfiguration(baseURL: url)
)
.get()
.timeout(10)
.intercept(ProfileImageDataCachePolicyInterceptor())
.validate(.successStatusCode)
.raw()
.data
} catch {
FirebaseCrashlyticsHelper.record(
error,
domain: "\(CrashlyticsError.domain).\(CrashlyticsError.Code.fetchImageData)",
code: CrashlyticsError.Code.fetchImageData.rawValue
)
throw error
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ import FirebaseMessaging
import UserNotifications

final class PushMessagingServiceImpl: NSObject, PushMessagingService {
private enum CrashlyticsError {
static let domain = "DevLogInfra.PushMessagingServiceImpl"

enum Code: Int {
case fetchFCMToken = 1
}
}

private weak var delegate: PushMessagingServiceDelegate?

func setDelegate(_ delegate: PushMessagingServiceDelegate?) {
Expand Down Expand Up @@ -42,6 +50,11 @@ final class PushMessagingServiceImpl: NSObject, PushMessagingService {
if error.isMissingAPNSTokenForFCMToken {
return nil
}
FirebaseCrashlyticsHelper.record(
error,
domain: "\(CrashlyticsError.domain).\(CrashlyticsError.Code.fetchFCMToken)",
code: CrashlyticsError.Code.fetchFCMToken.rawValue
)
throw error
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@ import DevLogCore
import DevLogData

final class PushNotificationServiceImpl: PushNotificationService {
private enum CrashlyticsError {
static let domain = "DevLogInfra.PushNotificationServiceImpl"

enum Code: Int {
case fetchPushNotificationEnabled = 1
case fetchPushNotificationTime
case updatePushNotificationSettings
case requestNotifications
case observeNotifications
case observeUnreadPushCount
case deleteNotification
case undoDeleteNotification
case toggleNotificationRead
}
}

private enum FunctionName: String {
case requestPushNotificationDeletion
case undoPushNotificationDeletion
Expand Down Expand Up @@ -44,6 +60,7 @@ final class PushNotificationServiceImpl: PushNotificationService {
throw FirestoreError.dataNotFound("allowPushNotification")
} catch {
logger.error("Failed to fetch push notification status", error: error)
record(error, code: .fetchPushNotificationEnabled)
throw error
}
}
Expand Down Expand Up @@ -72,6 +89,7 @@ final class PushNotificationServiceImpl: PushNotificationService {
return DateComponents(hour: hour, minute: minute)
} catch {
logger.error("Failed to fetch push notification time", error: error)
record(error, code: .fetchPushNotificationTime)
throw error
}
}
Expand Down Expand Up @@ -102,6 +120,7 @@ final class PushNotificationServiceImpl: PushNotificationService {
logger.info("Successfully updated push notification settings")
} catch {
logger.error("Failed to update push notification settings", error: error)
record(error, code: .updatePushNotificationSettings)
throw error
}
}
Expand Down Expand Up @@ -144,6 +163,7 @@ final class PushNotificationServiceImpl: PushNotificationService {
return PushNotificationPageResponse(items: items, nextCursor: nextCursor)
} catch {
logger.error("Failed to request notifications", error: error)
record(error, code: .requestNotifications)
throw error
}
}
Expand All @@ -160,6 +180,7 @@ final class PushNotificationServiceImpl: PushNotificationService {
.limit(to: pageLimit)
.addSnapshotListener { [weak self] snapshot, error in
if let error {
Self.record(error, code: .observeNotifications)
subject.send(completion: .failure(error))
return
}
Expand Down Expand Up @@ -190,6 +211,7 @@ final class PushNotificationServiceImpl: PushNotificationService {
.whereField(PushNotificationFieldKey.isDeleted.rawValue, isEqualTo: false)
.addSnapshotListener { snapshot, error in
if let error {
Self.record(error, code: .observeUnreadPushCount)
subject.send(completion: .failure(error))
return
}
Expand All @@ -213,6 +235,7 @@ final class PushNotificationServiceImpl: PushNotificationService {
_ = try await function.call(["notificationId": notificationID])
} catch {
logger.error("Failed to request notification deletion", error: error)
record(error, code: .deleteNotification)
throw error
}
}
Expand All @@ -225,6 +248,7 @@ final class PushNotificationServiceImpl: PushNotificationService {
_ = try await function.call(["notificationId": notificationID])
} catch {
logger.error("Failed to undo notification deletion", error: error)
record(error, code: .undoDeleteNotification)
throw error
}
}
Expand Down Expand Up @@ -259,12 +283,25 @@ final class PushNotificationServiceImpl: PushNotificationService {
logger.info("Successfully toggled notification read")
} catch {
logger.error("Failed to toggle notification read", error: error)
record(error, code: .toggleNotificationRead)
throw error
}
}
}

private extension PushNotificationServiceImpl {
private static func record(_ error: Error, code: CrashlyticsError.Code) {
FirebaseCrashlyticsHelper.record(
error,
domain: "\(CrashlyticsError.domain).\(code)",
code: code.rawValue
)
}

private func record(_ error: Error, code: CrashlyticsError.Code) {
Self.record(error, code: code)
}

func makeQuery(
uid: String,
query: PushNotificationQuery
Expand Down
Loading
Loading