diff --git a/app/src/bcsc-theme/api/client.ts b/app/src/bcsc-theme/api/client.ts index 03826ee64..6e9560437 100644 --- a/app/src/bcsc-theme/api/client.ts +++ b/app/src/bcsc-theme/api/client.ts @@ -190,6 +190,7 @@ class BCSCService { } private async handleRequest(config: InternalAxiosRequestConfig): Promise { + this.logger.info(`Handling request for URL: ${String(config.url)}`) // skip processing if skipBearerAuth is set in the config if (config.skipBearerAuth) { return config @@ -203,7 +204,8 @@ class BCSCService { config.headers.set('Authorization', `Bearer ${this.tokens.access_token}`) } - this.logger.debug(`${String(config.method)}: ${String(config.url)}`, {}) + this.logger.debug(`Sending request to ${String(config.url)} with method ${String(config.method)}`, config as any) + return config } diff --git a/app/src/bcsc-theme/hooks/useInitializeBcscApi.tsx b/app/src/bcsc-theme/hooks/useInitializeBcscApi.tsx index 3315756c9..e822d0cc6 100644 --- a/app/src/bcsc-theme/hooks/useInitializeBcscApi.tsx +++ b/app/src/bcsc-theme/hooks/useInitializeBcscApi.tsx @@ -22,10 +22,14 @@ const useInitializeBcscApi = () => { const asyncEffect = async () => { try { await client.fetchEndpointsAndConfig(client.baseURL) + logger.debug('Fetched BCSC endpoints and configuration', { + baseURL: client.baseURL, + }) } catch (error) { logger.error('Failed to fetch BCSC endpoints', { message: error instanceof Error ? error.message : String(error), }) + dispatch({ type: DispatchAction.BANNER_MESSAGES, payload: [ @@ -42,8 +46,9 @@ const useInitializeBcscApi = () => { try { await registration.register() + logger.debug('BCSC registration successful') } catch (error) { - logger.error(`Error during registration: ${error}`) + logger.error(`Error during registration.`, error as Error) } try { @@ -69,7 +74,7 @@ const useInitializeBcscApi = () => { dispatch({ type: BCDispatchAction.UPDATE_VERIFIED, payload: [true] }) } } catch (error) { - logger.error(`Error setting API client tokens: ${error}`) + logger.error(`Error setting API client tokens.`, error as Error) } finally { setLoading(false) } diff --git a/packages/bcsc-core/ios/BcscCore.swift b/packages/bcsc-core/ios/BcscCore.swift index 73239e355..99f4a70e8 100644 --- a/packages/bcsc-core/ios/BcscCore.swift +++ b/packages/bcsc-core/ios/BcscCore.swift @@ -2,6 +2,7 @@ import CryptoKit import Foundation import React + enum AccountSecurityMethod: String { case pinNoDeviceAuth = "app_pin_no_device_authn" case pinWithDeviceAuth = "app_pin_has_device_authn" @@ -33,6 +34,7 @@ enum DeviceInfoKeys { @objcMembers @objc(BcscCore) class BcscCore: NSObject { + let logger = AppLogger(subsystem: Bundle.main.bundleIdentifier ?? "ca.bc.gov.id.servicescard", category: "BcscCore") static let generalizedOsName = "iOS" static let provider = "https://idsit.gov.bc.ca/device/" static let clientName = "BC Services Wallet" @@ -144,7 +146,7 @@ class BcscCore: NSObject { SecItemDelete(query as CFDictionary) } - print("BcscCore: Keychain cleared for this app.") + logger.log("Keychain cleared for this app.") } // MARK: - Public Methods @@ -229,60 +231,60 @@ class BcscCore: NSObject { let baseId = components.joined(separator: "/") // Reconstruct the base part of the ID let newKeyId = "\(baseId)/\(numericSuffix)" - print( - "BcscCore: generateKeyPair (private) - Latest key found: \(existingTag). Attempting to generate new incremented key with ID: \(newKeyId)" + logger.log( + "generateKeyPair - Latest key found: \(existingTag). Attempting to generate new incremented key with ID: \(newKeyId)" ) do { // Assuming default keyType and keySize are handled by KeyPairManager.generateKeyPair or are acceptable. _ = try keyPairManager.generateKeyPair(withLabel: newKeyId) - print( - "BcscCore: generateKeyPair (private) - Successfully generated new incremented key with ID: \(newKeyId)" + logger.log( + "generateKeyPair - Successfully generated new incremented key with ID: \(newKeyId)" ) return newKeyId } catch { - print( - "BcscCore: generateKeyPair (private) - Failed to generate new incremented key with ID \(newKeyId): \(error.localizedDescription)." + logger.error( + "generateKeyPair - Failed to generate new incremented key with ID \(newKeyId): \(error.localizedDescription)." ) return nil // Failed to generate the specifically requested incremented key. } } else { // Parsing the existing tag failed (e.g., not in expected format or last part not a number). // Fallback: generate a completely new key using a fresh initial ID pattern. - print( - "BcscCore: generateKeyPair (private) - Could not parse or increment existing key tag: \(existingTag). Attempting to generate a new key with a fresh initial ID pattern." + logger.warning( + "generateKeyPair - Could not parse or increment existing key tag: \(existingTag). Attempting to generate a new key with a fresh initial ID pattern." ) // Use the same pattern for the new key ID as in the 'no keys found' case for consistency, but with a new UUID. let freshGeneratedKeyId = "\(BcscCore.provider)/\(UUID().uuidString)/1" - print( - "BcscCore: generateKeyPair (private) - Attempting to generate a new key with ID: \(freshGeneratedKeyId) due to parsing failure of existing key." + logger.log( + "generateKeyPair - Attempting to generate a new key with ID: \(freshGeneratedKeyId) due to parsing failure of existing key." ) do { _ = try keyPairManager.generateKeyPair(withLabel: freshGeneratedKeyId) - print( - "BcscCore: generateKeyPair (private) - Successfully generated new key with ID: \(freshGeneratedKeyId) after parsing failure." + logger.log( + "generateKeyPair - Successfully generated new key with ID: \(freshGeneratedKeyId) after parsing failure." ) return freshGeneratedKeyId } catch { - print( - "BcscCore: generateKeyPair (private) - Failed to generate new key with ID \(freshGeneratedKeyId) after parsing failure: \(error.localizedDescription)" + logger.error( + "generateKeyPair - Failed to generate new key with ID \(freshGeneratedKeyId) after parsing failure: \(error.localizedDescription)" ) return nil } } } else { // No keys found, attempt to generate a new one - print( - "BcscCore: generateKeyPair (private) - No keys found. Attempting to generate a new key with ID: \(initialKeyId)" + logger.log( + "generateKeyPair - No keys found. Attempting to generate a new key with ID: \(initialKeyId)" ) do { _ = try keyPairManager.generateKeyPair(withLabel: initialKeyId) // Assuming default keyType and keySize are handled by this method or are acceptable. - print( - "BcscCore: generateKeyPair (private) - Successfully generated new key with ID: \(initialKeyId)" + logger.log( + "generateKeyPair - Successfully generated new key with ID: \(initialKeyId)" ) return initialKeyId } catch { - print( - "BcscCore: generateKeyPair (private) - Failed to generate new key with ID \(initialKeyId): \(error.localizedDescription)" + logger.error( + "generateKeyPair - Failed to generate new key with ID \(initialKeyId): \(error.localizedDescription)" ) return nil } @@ -375,7 +377,7 @@ class BcscCore: NSObject { // Add any additional claims for (key, value) in claims { - print(key, value) + logger.log("\(key), \(value)") builder.claim(name: key as! String, value: value) } @@ -393,7 +395,7 @@ class BcscCore: NSObject { _ account: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock ) { - print("BcscCore: setAccount called with account: \(account)") + logger.log("setAccount called with account: \(account)") let accountID = UUID().uuidString let storage = StorageService() @@ -444,7 +446,7 @@ class BcscCore: NSObject { ) if success { - print("BcscCore: setAccount - Account successfully stored") + logger.log("setAccount - Account successfully stored") resolve(nil) } else { reject("E_ACCOUNT_STORAGE_FAILED", "Failed to store account data", nil) @@ -659,7 +661,7 @@ class BcscCore: NSObject { ] do { - // print("BcscCore: getDynamicClientRegistrationBody - Client Registration Data: \(clientRegistrationData)") + // logger.log("BcscCore: getDynamicClientRegistrationBody - Client Registration Data: \(clientRegistrationData)") let jsonData = try JSONSerialization.data(withJSONObject: clientRegistrationData, options: []) let jsonString = String(data: jsonData, encoding: .utf8) ?? "{}" @@ -810,7 +812,7 @@ class BcscCore: NSObject { return // Error already handled in makeSignedJWT } - print("signedJWT: \(signedJWT)") + logger.log("signedJWT: \(signedJWT)") // Encrypt the signed JWT with the provided public key do { diff --git a/packages/bcsc-core/ios/Logger.swift b/packages/bcsc-core/ios/Logger.swift new file mode 100644 index 000000000..720636514 --- /dev/null +++ b/packages/bcsc-core/ios/Logger.swift @@ -0,0 +1,69 @@ +import Foundation +import os + +struct AppLogger { + private let subsystem: String + private let category: String + + @available(iOS 14.0, *) + private var osLogger: os.Logger { + os.Logger(subsystem: subsystem, category: category) + } + + private var legacyLogger: OSLog { + OSLog(subsystem: subsystem, category: category) + } + + init(subsystem: String, category: String) { + self.subsystem = subsystem + self.category = category + } + + func log(_ message: String) { + if #available(iOS 14.0, *) { + osLogger.log("\(message, privacy: .public)") + } else { + os_log("%{public}s", log: legacyLogger, type: .default, message) + } + } + + func info(_ message: String) { + if #available(iOS 14.0, *) { + osLogger.info("\(message, privacy: .public)") + } else { + os_log("%{public}s", log: legacyLogger, type: .info, message) + } + } + + func debug(_ message: String) { + if #available(iOS 14.0, *) { + osLogger.debug("\(message, privacy: .public)") + } else { + os_log("%{public}s", log: legacyLogger, type: .debug, message) + } + } + + func error(_ message: String) { + if #available(iOS 14.0, *) { + osLogger.error("\(message, privacy: .public)") + } else { + os_log("%{public}s", log: legacyLogger, type: .error, message) + } + } + + func warning(_ message: String) { + if #available(iOS 14.0, *) { + osLogger.warning("\(message, privacy: .public)") + } else { + os_log("%{public}s", log: legacyLogger, type: .error, message) // .warning is not available, fallback to .error + } + } + + func fault(_ message: String) { + if #available(iOS 14.0, *) { + osLogger.fault("\(message, privacy: .public)") + } else { + os_log("%{public}s", log: legacyLogger, type: .fault, message) + } + } +} diff --git a/packages/bcsc-core/ios/StorageService.swift b/packages/bcsc-core/ios/StorageService.swift index 16f64fac7..78d399c7d 100644 --- a/packages/bcsc-core/ios/StorageService.swift +++ b/packages/bcsc-core/ios/StorageService.swift @@ -28,6 +28,7 @@ enum AccountFiles: String { } class StorageService { + let logger = AppLogger(subsystem: Bundle.main.bundleIdentifier ?? "ca.bc.gov.id.servicescard", category: "StorageService") var currentBundleID: String { return Bundle.main.bundleIdentifier ?? "ca.bc.gov.id.servicescard" } @@ -60,7 +61,7 @@ class StorageService { .appendingPathComponent(accountListURLComponent) guard FileManager.default.fileExists(atPath: accountListFileUrl.path) else { - print("StorageService: Error - account_list file does not exist at \(accountListFileUrl.path).") + logger.error("account_list file does not exist at \(accountListFileUrl.path).") return nil } @@ -69,14 +70,14 @@ class StorageService { if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], let accounts = json["accounts"] as? [String], let firstAccountID = accounts.first, !firstAccountID.isEmpty { - // print("StorageService: Successfully loaded account ID \(firstAccountID) from account_list.") + // logger.log("StorageService: Successfully loaded account ID \(firstAccountID) from account_list.") return firstAccountID } else { - print("StorageService: Error - Failed to parse account_list JSON or accounts array is empty/first ID is empty.") + logger.error("Failed to parse account_list JSON or accounts array is empty/first ID is empty.") return nil } } catch { - print("StorageService: Error - Could not access or read account_list: \(error).") + logger.error("Could not access or read account_list: \(error).") return nil } } @@ -88,7 +89,7 @@ class StorageService { func readData(file: AccountFiles, pathDirectory: FileManager.SearchPathDirectory) -> T? { // Added file parameter do { guard let accountID = self.currentAccountID else { - print("StorageService: Error - currentAccountID is nil. Cannot read data.") + logger.error("currentAccountID is nil. Cannot read data.") return nil } let rootDirectoryURL = try FileManager.default.url(for: pathDirectory, in: .userDomainMask, appropriateFor: nil, create: false) @@ -110,14 +111,14 @@ class StorageService { } let data = try Data(contentsOf: fileUrl) - print("Data read from file: \(data)") + logger.log("Data read from file: \(data)") if let obj: T = try? decodeArchivedObject(from: data) { - print("Decoded object: \(obj)") + logger.log("Decoded object: \(obj)") return obj } - print("Failed to decode object from data.") + logger.error("Failed to decode object from data.") return nil } catch { @@ -133,7 +134,7 @@ class StorageService { do { // Get the current account ID first guard let accountID = self.currentAccountID else { - print("StorageService: Error - currentAccountID is nil. Cannot write data.") + logger.error("currentAccountID is nil. Cannot write data.") return false } @@ -150,10 +151,10 @@ class StorageService { // Write the encoded data to file try encodedData.write(to: fileUrl) - print("StorageService: Successfully wrote data to file: \(fileUrl.path)") + logger.log("Successfully wrote data to file: \(fileUrl.path)") return true } catch { - print("StorageService: Error writing data: \(error)") + logger.error("Error writing data: \(error)") return false } } @@ -167,7 +168,7 @@ class StorageService { // Check if the account_list file already exists guard !FileManager.default.fileExists(atPath: accountListPath.path) else { - print("StorageService: account_list file already exists at \(accountListPath.path)") + logger.log("account_list file already exists at \(accountListPath.path)") return } @@ -181,9 +182,9 @@ class StorageService { let accountDirectory = baseURL.appendingPathComponent(accountID) if !FileManager.default.fileExists(atPath: accountDirectory.path) { try FileManager.default.createDirectory(at: accountDirectory, withIntermediateDirectories: true, attributes: nil) - print("StorageService: Created account directory at \(accountDirectory.path)") + logger.log("Created account directory at \(accountDirectory.path)") } else { - print("StorageService: Account directory already exists at \(accountDirectory.path)") + logger.log("Account directory already exists at \(accountDirectory.path)") } // Convert to JSON data and write to file @@ -201,9 +202,9 @@ class StorageService { if T.self != NSDictionary.self { let archivedClassName = "\(moduleName).\(className)" NSKeyedArchiver.setClassName(archivedClassName, for: T.self) - print("Encoding class: \(archivedClassName)") + logger.log("Encoding class: \(archivedClassName)") } else { - print("Skipping class registration for NSDictionary") + logger.log("Skipping class registration for NSDictionary") } let archiver = NSKeyedArchiver(requiringSecureCoding: false) @@ -233,9 +234,9 @@ class StorageService { if T.self != NSDictionary.self { let archivedClassName = "\(moduleName).\(className)" NSKeyedUnarchiver.setClass(T.self, forClassName: archivedClassName) - print("Decoding classx: \(archivedClassName)") + logger.log("Decoding classx: \(archivedClassName)") } else { - print("Skipping class registration for NSDictionary") + logger.log("Skipping class registration for NSDictionary") } let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)