Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ jobs:
cache: true

- name: Install SwiftLint
env:
HOMEBREW_REQUIRE_TAP_TRUST: "1"
shell: bash
run: |
set -euo pipefail
Expand Down Expand Up @@ -264,6 +266,8 @@ jobs:
cache: true

- name: Install SwiftLint
env:
HOMEBREW_REQUIRE_TAP_TRUST: "1"
shell: bash
run: |
set -euo pipefail
Expand Down
4 changes: 4 additions & 0 deletions Application/DevLogApp/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,13 @@ let project = Project(
],
debug: [
"APS_ENVIRONMENT": "development",
"DEBUG_INFORMATION_FORMAT": "dwarf",
"INFOPLIST_KEY_FirebaseCrashlyticsCollectionEnabled": "NO",
],
release: [
"APS_ENVIRONMENT": "production",
"DEBUG_INFORMATION_FORMAT": "dwarf-with-dsym",
"INFOPLIST_KEY_FirebaseCrashlyticsCollectionEnabled": "YES",
]
)
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// 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] = [
NSUnderlyingErrorKey: nsError,
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()
enableCrashlyticsCollectionIfNeeded()
Self.isConfigured = true
}
}

private extension FirebaseAppServiceImpl {
func enableCrashlyticsCollectionIfNeeded() {
#if !DEBUG
Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(true)
#endif
}
}
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