diff --git a/Package.resolved b/Package.resolved index 79ee3fa..0bb1d25 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,6 +1,15 @@ { "object": { "pins": [ + { + "package": "Segment", + "repositoryURL": "https://github.com/segmentio/analytics-swift", + "state": { + "branch": null, + "revision": "5d3a762da5412d324205a58358b95e1da334c21e", + "version": "1.8.0" + } + }, { "package": "BigInt", "repositoryURL": "https://github.com/attaswift/BigInt", @@ -19,6 +28,42 @@ "version": "2.0.0" } }, + { + "package": "FetchNodeDetails", + "repositoryURL": "https://github.com/torusresearch/fetch-node-details-swift", + "state": { + "branch": null, + "revision": "5b42dd1675f8a51ffe64feb688db7b1d764d5fc0", + "version": "8.0.1" + } + }, + { + "package": "JSONSafeEncoding", + "repositoryURL": "https://github.com/segmentio/jsonsafeencoding-swift.git", + "state": { + "branch": null, + "revision": "af6a8b360984085e36c6341b21ecb35c12f47ebd", + "version": "2.0.0" + } + }, + { + "package": "jwt-kit", + "repositoryURL": "https://github.com/vapor/jwt-kit.git", + "state": { + "branch": null, + "revision": "13e7513b3ba0afa13967daf77af2fb4ad087306c", + "version": "4.13.5" + } + }, + { + "package": "JWTDecode", + "repositoryURL": "https://github.com/auth0/JWTDecode.swift.git", + "state": { + "branch": null, + "revision": "36a5ce735a61c4bc119593f43ce2c027b4ca7392", + "version": "3.3.0" + } + }, { "package": "KeychainSwift", "repositoryURL": "https://github.com/evgenyneu/keychain-swift.git", @@ -36,6 +81,42 @@ "revision": "de18a6b3d98f67b3c77f84ebab73f7a51807a503", "version": "6.1.0" } + }, + { + "package": "Sovran", + "repositoryURL": "https://github.com/segmentio/sovran-swift.git", + "state": { + "branch": null, + "revision": "24867f3e4ac62027db9827112135e6531b6f4051", + "version": "1.1.2" + } + }, + { + "package": "swift-asn1", + "repositoryURL": "https://github.com/apple/swift-asn1.git", + "state": { + "branch": null, + "revision": "f70225981241859eb4aa1a18a75531d26637c8cc", + "version": "1.4.0" + } + }, + { + "package": "swift-crypto", + "repositoryURL": "https://github.com/apple/swift-crypto.git", + "state": { + "branch": null, + "revision": "176abc28e002a9952470f08745cd26fad9286776", + "version": "3.13.3" + } + }, + { + "package": "TorusUtils", + "repositoryURL": "https://github.com/torusresearch/torus-utils-swift.git", + "state": { + "branch": null, + "revision": "235a70839d3ff5723402df95d665b15b8c551ad8", + "version": "10.0.1" + } } ] }, diff --git a/Package.swift b/Package.swift index f19fd4e..ed52e6f 100644 --- a/Package.swift +++ b/Package.swift @@ -19,7 +19,8 @@ let package = Package( .package(url: "https://github.com/attaswift/BigInt.git", from: "5.3.0"), .package(url: "https://github.com/torusresearch/torus-utils-swift.git", from: "10.0.1"), .package(url: "https://github.com/vapor/jwt-kit.git", from: "4.0.0"), - .package(url: "https://github.com/auth0/JWTDecode.swift.git", from: "3.2.0") + .package(url: "https://github.com/auth0/JWTDecode.swift.git", from: "3.2.0"), + .package(url: "https://github.com/segmentio/analytics-swift.git", from: "1.8.0") ], targets: [ .target( @@ -30,7 +31,8 @@ let package = Package( "SessionManager", "BigInt", .product(name: "TorusUtils", package: "torus-utils-swift"), - .product(name: "JWTDecode", package: "JWTDecode.swift") + .product(name: "JWTDecode", package: "JWTDecode.swift"), + .product(name: "Segment", package: "analytics-swift") ]), .testTarget( name: "Web3AuthTests", diff --git a/Sources/Web3Auth/Types.swift b/Sources/Web3Auth/Types.swift index ce06e6d..c732ad9 100644 --- a/Sources/Web3Auth/Types.swift +++ b/Sources/Web3Auth/Types.swift @@ -163,6 +163,26 @@ public struct AuthConnectionConfig: Codable { } public struct Web3AuthOptions: Codable { + + public var isFlutterAnalytics: Bool = false + public var sdkVersion: String? = nil + + public mutating func setFlutterAnalytics(_ isFlutter: Bool?, sdkVersion: String? = nil) { + self.isFlutterAnalytics = isFlutter ?? false + self.sdkVersion = sdkVersion + } + + public func getSdkName() -> String { + return isFlutterAnalytics ? AnalyticsSdkType.flutter : AnalyticsSdkType.ios + } + + public func getSdkVersion() -> String { + if let version = sdkVersion, !version.isEmpty { + return version // Flutter SDK version + } + return AnalyticsEvents.iosSdkVersion // Default iOS SDK version + } + public init(clientId: String, redirectUrl: String, originData: [String: String]? = nil, authBuildEnv: BuildEnv? = .production, sdkUrl: String? = nil, storageServerUrl: String? = nil,sessionSocketUrl: String? = nil, authConnectionConfig: [AuthConnectionConfig]? = nil, whiteLabel: WhiteLabelData? = nil, dashboardUrl: String? = nil, accountAbstractionConfig: String? = nil, walletSdkUrl: String? = nil, @@ -248,7 +268,7 @@ public struct Web3AuthOptions: Codable { var web3AuthNetwork: Web3AuthNetwork var useSFAKey: Bool? var walletServicesConfig: WalletServicesConfig? - let mfaSettings: MfaSettings? + var mfaSettings: MfaSettings? enum CodingKeys: String, CodingKey { @@ -731,6 +751,8 @@ public struct ProjectConfigResponse: Codable { public var walletConnectEnabled: Bool? = nil public var walletConnectProjectId: String? = nil public var whitelabel: WhiteLabelData? = nil + public var teamId: Int? = nil + public var mfaSettings: MfaSettings? enum CodingKeys: String, CodingKey { case userDataInIdToken @@ -745,6 +767,8 @@ public struct ProjectConfigResponse: Codable { case walletConnectEnabled = "wallet_connect_enabled" case walletConnectProjectId case whitelabel + case teamId + case mfaSettings } public init(from decoder: Decoder) throws { @@ -761,6 +785,8 @@ public struct ProjectConfigResponse: Codable { self.walletConnectEnabled = try container.decodeIfPresent(Bool.self, forKey: .walletConnectEnabled) ?? false self.walletConnectProjectId = try container.decodeIfPresent(String.self, forKey: .walletConnectProjectId) self.whitelabel = try container.decodeIfPresent(WhiteLabelData.self, forKey: .whitelabel) + self.teamId = try container.decodeIfPresent(Int.self, forKey: .teamId) + self.mfaSettings = try container.decodeIfPresent(MfaSettings.self, forKey: .mfaSettings) } } @@ -810,3 +836,17 @@ public struct Web3AuthSubVerifierInfo: Codable { var idToken: String } +extension MfaSettings { + func merge(with other: MfaSettings?) -> MfaSettings { + guard let other = other else { return self } + return MfaSettings( + deviceShareFactor: other.deviceShareFactor ?? self.deviceShareFactor, + backUpShareFactor: other.backUpShareFactor ?? self.backUpShareFactor, + socialBackupFactor: other.socialBackupFactor ?? self.socialBackupFactor, + passwordFactor: other.passwordFactor ?? self.passwordFactor, + passkeysFactor: other.passkeysFactor ?? self.passkeysFactor, + authenticatorFactor: other.authenticatorFactor ?? self.authenticatorFactor + ) + } +} + diff --git a/Sources/Web3Auth/Web3Auth.swift b/Sources/Web3Auth/Web3Auth.swift index 2d65cdf..b595506 100644 --- a/Sources/Web3Auth/Web3Auth.swift +++ b/Sources/Web3Auth/Web3Auth.swift @@ -34,6 +34,7 @@ public class Web3Auth: NSObject { private var projectConfigResponse: ProjectConfigResponse? = nil let nodeDetailManager: NodeDetailManager let torusUtils: TorusUtils + private let startTime: Int64 = Int64(Date().timeIntervalSince1970 * 1000) let SIGNER_MAP: [Web3AuthNetwork: String] = [ .MAINNET: "https://signer.web3auth.io", @@ -55,6 +56,25 @@ public class Web3Auth: NSObject { - returns: Web3Auth component. */ public init(options: Web3AuthOptions) async throws { + + // Segment analytics Initilization + AnalyticsManager.shared.initialize() + + AnalyticsManager.shared.identify( + userId: options.clientId, + traits: [ + "web3auth_client_id": options.clientId, + "web3auth_network": options.web3AuthNetwork + ] + ) + + AnalyticsManager.shared.setGlobalProperties([ + "sdk_name": options.getSdkName(), + "sdk_version": options.getSdkVersion(), + "web3auth_client_id": options.clientId, + "web3auth_network": options.web3AuthNetwork + ]) + web3AuthOptions = options Router.baseURL = SIGNER_MAP[options.web3AuthNetwork] ?? "" let isSFA = KeychainHelper.shared.get(forKey: "isSFA", as: Bool.self) @@ -87,9 +107,23 @@ public class Web3Auth: NSObject { } public func logout() async throws { - guard let web3AuthResponse = web3AuthResponse else { throw Web3AuthError.noUserFound } + AnalyticsManager.shared.trackEvent( + AnalyticsEvents.logoutStarted + ) + guard let web3AuthResponse = web3AuthResponse else { + AnalyticsManager.shared.trackEvent( + AnalyticsEvents.logoutFailed, + properties: [ + "error_message": "Logout Failed" + ] + ) + throw Web3AuthError.noUserFound + } try await sessionManager.invalidateSession() SessionManager.deleteSessionIdFromStorage() + AnalyticsManager.shared.trackEvent( + AnalyticsEvents.logoutCompleted + ) if let authConnectionId = web3AuthResponse.userInfo?.authConnectionId, let dappShare = KeychainManager.shared.getDappShare(authConnectionId: authConnectionId) { KeychainManager.shared.delete(key: .custom(dappShare)) } @@ -227,8 +261,44 @@ public class Web3Auth: NSObject { } self.web3AuthResponse = loginDetails + var analyticsProps: [String: Any] = [ + "connector": "auth", + "auth_connection": loginParams.authConnection ?? "", + "auth_connection_id": loginParams.authConnectionId?.description ?? "", + "group_auth_connection_id": loginParams.groupedAuthConnectionId?.description ?? "", + "chain_id": web3AuthOptions.defaultChainId?.description ?? "", + "dapp_url": loginParams.dappUrl ?? "", + "chains": web3AuthOptions.chains?.description ?? "[]", + "integration_type": web3AuthOptions.getSdkName(), + "is_sfa": false + ] + + analyticsProps["duration"] = Int(Date().timeIntervalSince1970) * 1000 - Int(startTime) + + AnalyticsManager.shared.trackEvent( + AnalyticsEvents.connectionCompleted, + properties: analyticsProps + ) + continuation.resume(returning: loginDetails) } catch { + let duration = Date().timeIntervalSince1970 * 1000 - Double(startTime) + + let properties: [String: Any] = [ + "connector": "auth", + "auth_connection": loginParams.authConnection ?? "", + "auth_connection_id": loginParams.authConnectionId?.description ?? "", + "group_auth_connection_id": loginParams.groupedAuthConnectionId?.description ?? "", + "chain_id": web3AuthOptions.defaultChainId?.description, + "dapp_url": loginParams.dappUrl ?? "", + "chains": web3AuthOptions.chains?.description ?? "[]", + "auth_ux_mode": "popup", + "is_sfa": false, + "duration": duration, + "error_message": error ?? Web3AuthError.unknownError + ] + + AnalyticsManager.shared.trackEvent(AnalyticsEvents.connectionFailed, properties: properties) continuation.resume(throwing: Web3AuthError.unknownError) } } @@ -250,8 +320,25 @@ public class Web3Auth: NSObject { allowedOrigin: web3AuthOptions.redirectUrl, sessionNamespace: (loginParams.idToken?.isEmpty == false) ? "sfa" : "" ) + + var analyticsProps: [String: Any] = [ + "connector": "auth", + "auth_connection": loginParams.authConnection, + "auth_connection_id": loginParams.authConnectionId?.description ?? "", + "group_auth_connection_id": loginParams.groupedAuthConnectionId?.description ?? "", + "chain_id": web3AuthOptions.defaultChainId?.description ?? "", + "dapp_url": loginParams.dappUrl ?? "", + "chains": web3AuthOptions.chains?.description ?? "[]", + "auth_ux_mode": "popup" + ] + // Case 1: No idToken provided if loginParams.idToken?.isEmpty ?? true { + analyticsProps["is_sfa"] = false + AnalyticsManager.shared.trackEvent( + AnalyticsEvents.connectionStarted, + properties: analyticsProps + ) if let loginHint = loginParams.loginHint, !loginHint.isEmpty { // Create or update extraLoginOptions with loginHint var updatedExtraLoginOptions = loginParams.extraLoginOptions @@ -272,6 +359,11 @@ public class Web3Auth: NSObject { // Case 2: idToken exists if let groupedId = loginParams.groupedAuthConnectionId, !groupedId.isEmpty { + analyticsProps["is_sfa"] = true + AnalyticsManager.shared.trackEvent( + AnalyticsEvents.connectionStarted, + properties: analyticsProps + ) let newLoginParams = LoginParams( authConnection: .CUSTOM, authConnectionId: groupedId, @@ -286,6 +378,11 @@ public class Web3Auth: NSObject { KeychainHelper.shared.save(true, forKey: KeychainKeys.isSFA) return try await connect(loginParams: newLoginParams, subVerifierInfoArray: subVerifierInfoArray) } else { + analyticsProps["is_sfa"] = true + AnalyticsManager.shared.trackEvent( + AnalyticsEvents.connectionStarted, + properties: analyticsProps + ) KeychainHelper.shared.save(true, forKey: KeychainKeys.isSFA) return try await connect(loginParams: loginParams) // SFA login fallback } @@ -366,6 +463,23 @@ public class Web3Auth: NSObject { dappShare: nil, idToken: nil, oAuthIdToken: nil, oAuthAccessToken: nil, isMfaEnabled: false, authConnection: "custom", appState: nil) } catch { throw Web3AuthError.inValidLogin + let duration = Date().timeIntervalSince1970 * 1000 - Double(startTime) + + let properties: [String: Any] = [ + "connector": "auth", + "auth_connection": loginParams.authConnection ?? "", + "auth_connection_id": loginParams.authConnectionId?.description ?? "", + "group_auth_connection_id": loginParams.groupedAuthConnectionId?.description ?? "", + "chain_id": web3AuthOptions.defaultChainId?.description, + "dapp_url": loginParams.dappUrl ?? "", + "chains": web3AuthOptions.chains?.description ?? "[]", + "auth_ux_mode": "popup", + "is_sfa": true, + "duration": duration, + "error_message": Web3AuthError.inValidLogin + ] + + AnalyticsManager.shared.trackEvent(AnalyticsEvents.connectionFailed, properties: properties) } let sessionId = try SessionManager.generateRandomSessionID()! @@ -377,6 +491,24 @@ public class Web3Auth: NSObject { SessionManager.saveSessionIdToStorage(sessionId) sessionManager.setSessionId(sessionId: sessionId) + var analyticsProps: [String: Any] = [ + "connector": "auth", + "auth_connection": loginParams.authConnection.description, + "auth_connection_id": loginParams.authConnectionId?.description ?? "", + "group_auth_connection_id": loginParams.groupedAuthConnectionId?.description ?? "", + "chain_id": web3AuthOptions.defaultChainId?.description ?? "", + "dapp_url": loginParams.dappUrl ?? "", + "chains": web3AuthOptions.chains?.description ?? "[]", + "integration_type": web3AuthOptions.getSdkName(), + "is_sfa": true + ] + + analyticsProps["duration"] = Int(Date().timeIntervalSince1970) * 1000 - Int(startTime) + + AnalyticsManager.shared.trackEvent( + AnalyticsEvents.connectionCompleted, + properties: analyticsProps + ) //self.state = sfaKey return web3AuthResponse } @@ -403,6 +535,18 @@ public class Web3Auth: NSObject { public func enableMFA(_ loginParams: LoginParams? = nil) async throws -> Bool { // Note that this function can be called without login on restored session, so loginParams should not be optional. + let duration = Date().timeIntervalSince1970 * 1000 - Double(startTime) + + AnalyticsManager.shared.trackEvent( + AnalyticsEvents.mfaEnablementStarted, + properties: [ + "integration_type": web3AuthOptions.getSdkName(), + "dapp_url": loginParams?.dappUrl ?? "", + "connector": "auth", + "duration": duration + ] + ) + if web3AuthResponse?.userInfo?.isMfaEnabled == true { throw Web3AuthError.mfaAlreadyEnabled } @@ -432,7 +576,7 @@ public class Web3Auth: NSObject { let newSessionId = try SessionManager.generateRandomSessionID()! let loginIdObject: [String: String?] = [ "loginId": newSessionId, - "platform": "iOS", + "platform": web3AuthOptions.getSdkName(), ] let jsonEncoder = JSONEncoder() @@ -493,6 +637,26 @@ public class Web3Auth: NSObject { KeychainManager.shared.saveDappShare(userInfo: safeUserInfo) } self.web3AuthResponse = loginDetails + + var analyticsProps: [String: Any] = [ + "connector": "auth", + "auth_connection": loginParams?.authConnection ?? "", + "auth_connection_id": loginParams?.authConnectionId?.description ?? "", + "group_auth_connection_id": loginParams?.groupedAuthConnectionId?.description ?? "", + "chain_id": self.web3AuthOptions.defaultChainId?.description ?? "", + "dapp_url": loginParams?.dappUrl ?? "", + "chains": self.web3AuthOptions.chains?.description ?? "[]", + "integration_type": self.web3AuthOptions.getSdkName(), + "is_sfa": false + ] + + analyticsProps["duration"] = Int(Date().timeIntervalSince1970) * 1000 - Int(self.startTime) + + AnalyticsManager.shared.trackEvent( + AnalyticsEvents.mfaEnablementCompleted, + properties: analyticsProps + ) + continuation.resume(returning: true) } catch { continuation.resume(throwing: Web3AuthError.unknownError) @@ -512,6 +676,14 @@ public class Web3Auth: NSObject { } public func manageMFA(_ loginParams: LoginParams? = nil) async throws -> Bool { + AnalyticsManager.shared.trackEvent( + AnalyticsEvents.mfaManagementStarted, + properties: [ + "integration_type": web3AuthOptions.getSdkName(), + "dapp_url": loginParams?.dappUrl ?? "", + "connector": "auth" + ] + ) if web3AuthResponse?.userInfo?.isMfaEnabled == false { throw Web3AuthError.mfaNotEnabled } @@ -583,6 +755,25 @@ public class Web3Auth: NSObject { } return } + + var analyticsProps: [String: Any] = [ + "connector": "auth", + "auth_connection": loginParams?.authConnection ?? "", + "auth_connection_id": loginParams?.authConnectionId?.description ?? "", + "group_auth_connection_id": loginParams?.groupedAuthConnectionId?.description ?? "", + "chain_id": self.web3AuthOptions.defaultChainId?.description ?? "", + "dapp_url": loginParams?.dappUrl ?? "", + "chains": self.web3AuthOptions.chains?.description ?? "[]", + "integration_type": self.web3AuthOptions.getSdkName(), + "is_sfa": false + ] + + analyticsProps["duration"] = Int(Date().timeIntervalSince1970) * 1000 - Int(self.startTime) + + AnalyticsManager.shared.trackEvent( + AnalyticsEvents.mfaEnablementCompleted, + properties: analyticsProps + ) continuation.resume(returning: true) } @@ -590,6 +781,15 @@ public class Web3Auth: NSObject { self.authSession?.presentationContextProvider = self if !(self.authSession?.start() ?? false) { + let duration = Date().timeIntervalSince1970 * 1000 - Double(self.startTime) + + AnalyticsManager.shared.trackEvent( + AnalyticsEvents.mfaManagementFailed, + properties: [ + "duration": duration, + "error_message": "MFA Enablement Failed: Web3AuthError.unknownError" + ] + ) continuation.resume(throwing: Web3AuthError.unknownError) } } @@ -597,6 +797,13 @@ public class Web3Auth: NSObject { } public func showWalletUI(path: String? = "wallet") async throws { + AnalyticsManager.shared.trackEvent( + AnalyticsEvents.walletUIClicked, + properties: [ + "integration_type": web3AuthOptions.getSdkName(), + "dapp_url": loginParams?.dappUrl ?? "" + ] + ) let savedSessionId = SessionManager.getSessionIdFromStorage()! if !savedSessionId.isEmpty { var initOptionsJson = try JSONSerialization.jsonObject(with: JSONEncoder().encode(web3AuthOptions)) as! [String: Any] @@ -658,11 +865,23 @@ public class Web3Auth: NSObject { } } } else { + AnalyticsManager.shared.trackEvent( + AnalyticsEvents.walletServicesFailed, + properties: [ + "integration_type": web3AuthOptions.getSdkName(), + "dapp_url": loginParams?.dappUrl ?? "", + "duration": Int(Date().timeIntervalSince1970 * 1000) - Int(startTime), + "error": "Wallet Services Error: SessionId not found. Please login first." + ] + ) throw Web3AuthError.runtimeError("SessionId not found. Please login first.") } } public func request(method: String, requestParams: [Any], path: String? = "wallet/request", appState: String? = nil) async throws -> SignResponse? { + AnalyticsManager.shared.trackEvent( + AnalyticsEvents.requestFunctionStarted + ) let sessionId = SessionManager.getSessionIdFromStorage()! if !sessionId.isEmpty { var initOptionsJson = try JSONSerialization.jsonObject(with: JSONEncoder().encode(web3AuthOptions)) as! [String: Any] @@ -724,6 +943,14 @@ public class Web3Auth: NSObject { Task { let webViewController = await MainActor.run { WebViewController(redirectUrl: web3AuthOptions.redirectUrl, onSignResponse: { signResponse in + let duration = Date().timeIntervalSince1970 * 1000 - Double(self.startTime) + AnalyticsManager.shared.trackEvent( + AnalyticsEvents.requestFunctionCompleted, + properties: [ + "duration": duration + ] + ) + continuation.resume(returning: signResponse) }, onCancel: { continuation.resume(returning: nil) @@ -738,6 +965,14 @@ public class Web3Auth: NSObject { } } } else { + let duration = Date().timeIntervalSince1970 * 1000 - Double(self.startTime) + AnalyticsManager.shared.trackEvent( + AnalyticsEvents.requestFunctionFailed, + properties: [ + "duration": duration, + "error": "Request Function Error: SessionId not found. Please login first." + ] + ) throw Web3AuthError.runtimeError("SessionId not found. Please login first.") } } @@ -802,9 +1037,40 @@ public class Web3Auth: NSObject { let result = try decoder.decode(ProjectConfigResponse.self, from: data) // os_log("fetchProjectConfig API response is: %@", log: getTorusLogger(log: Web3AuthLogger.network, type: .info), type: .info, "\(String(describing: result))") projectConfigResponse = result + AnalyticsManager.shared.setGlobalProperties([ + "sdk_name": web3AuthOptions.getSdkName(), + "sdk_version": web3AuthOptions.getSdkVersion(), + "web3auth_client_id": web3AuthOptions.clientId, + "web3auth_network": web3AuthOptions.web3AuthNetwork, + "team_id" : projectConfigResponse?.teamId.toString() + ]) + + let duration = Int(Date().timeIntervalSince1970 * 1000) - Int(startTime) + let chainIds: [String] = web3AuthOptions.chains?.compactMap { $0.chainId } ?? [] + let properties: [String: Any] = [ + "chain_ids" : chainIds, + "chain_nameSpaces": ["eip155", "solana", "other"], + "logging_enabled": web3AuthOptions.enableLogging, + "auth_build_env": web3AuthOptions.authBuildEnv?.rawValue, + "auth_mfa_settings": web3AuthOptions.mfaSettings, + "whitelabel_logo_light_enabled": web3AuthOptions.whiteLabel?.logoLight != nil, + "whitelabel_logo_dark_enabled": web3AuthOptions.whiteLabel?.logoDark != nil, + "whitelabel_theme_mode": web3AuthOptions.whiteLabel?.theme as Any, + "duration": duration, + "integration_type": web3AuthOptions.getSdkName(), + "dapp_url": self.loginParams?.dappUrl as Any + ] + + AnalyticsManager.shared.trackEvent( + AnalyticsEvents.sdkInitializationCompleted, + properties: properties + ) + web3AuthOptions.originData = result.whitelist.signedUrls.merging(web3AuthOptions.originData ?? [:]) { _, new in new } web3AuthOptions.authConnectionConfig = (web3AuthOptions.authConnectionConfig ?? []) + (projectConfigResponse?.embeddedWalletAuth ?? []) + web3AuthOptions.mfaSettings = web3AuthOptions.mfaSettings?.merge(with: projectConfigResponse?.mfaSettings) + ?? projectConfigResponse?.mfaSettings if let whiteLabelData = result.whitelabel { web3AuthOptions.whiteLabel = web3AuthOptions.whiteLabel?.merge(with: whiteLabelData) ?? whiteLabelData if web3AuthOptions.walletServicesConfig == nil { @@ -818,6 +1084,16 @@ public class Web3Auth: NSObject { response = true } catch { //print("Decoding failed: \(error)") + let duration = Int(Date().timeIntervalSince1970 * 1000) - Int(startTime) + let properties: [String: Any] = [ + "integration_type": web3AuthOptions.getSdkName(), + "dapp_url": self.loginParams?.dappUrl ?? "", + "duration": duration, + "error_message": error + ] + + AnalyticsManager.shared.trackEvent(AnalyticsEvents.sdkInitializationFailed, properties: properties) + throw error } case let .failure(error): diff --git a/Sources/Web3Auth/analytics/AnalyticsManager.swift b/Sources/Web3Auth/analytics/AnalyticsManager.swift new file mode 100644 index 0000000..f697a4d --- /dev/null +++ b/Sources/Web3Auth/analytics/AnalyticsManager.swift @@ -0,0 +1,86 @@ +import Foundation +import Segment + +final class AnalyticsManager { + static let shared = AnalyticsManager() + + #if DEBUG + private let SEGMENT_WRITE_KEY = SegmentKeys.development + #else + private let SEGMENT_WRITE_KEY = SegmentKeys.production + #endif + + private(set) var analytics: Analytics? + private var isInitialized = false + private var globalProperties: [String: Any] = [:] + + private init() {} + + func initialize() { + guard !isInitialized else { return } + + let configuration = Configuration(writeKey: SEGMENT_WRITE_KEY) + let analyticsInstance = Analytics(configuration: configuration) + self.analytics = analyticsInstance + isInitialized = true + } + + func setGlobalProperties(_ properties: [String: Any]) { + globalProperties = properties + } + + func trackEvent(_ eventName: String, properties: [String: Any]? = nil) { + guard let analytics = analytics else { return } + + var combinedProps = globalProperties + properties?.forEach { combinedProps[$0.key] = $0.value } + + analytics.track(name: eventName, properties: combinedProps) + } + + func trackScreen(name: String, properties: [String: Any]? = nil) { + guard let analytics = analytics else { return } + + analytics.screen(title: name, properties: properties) + } + + func identify(userId: String, traits: [String: Any]? = nil) { + guard let analytics = analytics else { return } + analytics.identify(userId: userId, traits: traits ?? [:]) + } +} + +enum SegmentKeys { + static let production = "f6LbNqCeVRf512ggdME4b6CyflhF1tsX" + static let development = "rpE5pCcpA6ME2oFu2TbuVydhOXapjHs3" +} + +enum AnalyticsEvents { + static let sdkInitializationCompleted = "SDK Initialization Completed" + static let sdkInitializationFailed = "SDK Initialization Failed" + static let connectionStarted = "Connection Started" + static let connectionCompleted = "Connection Completed" + static let connectionFailed = "Connection Failed" + static let mfaEnablementStarted = "MFA Enablement Started" + static let mfaEnablementCompleted = "MFA Enablement Completed" + static let mfaEnablementFailed = "MFA Enablement Failed" + static let mfaManagementStarted = "MFA Management Started" + static let mfaManagementFailed = "MFA Management Failed" + static let mfaManagementCompleted = "MFA Management Completed" + static let walletUIClicked = "Wallet UI Clicked" + static let walletServicesFailed = "Wallet Services Failed" + static let logoutStarted = "Logout Started" + static let logoutCompleted = "Logout Completed" + static let logoutFailed = "Logout Failed" + static let requestFunctionStarted = "Request Function Started" + static let requestFunctionCompleted = "Request Function Completed" + static let requestFunctionFailed = "Request Function Failed" + + static let iosSdkVersion = "12.0.0" +} + +enum AnalyticsSdkType { + static let ios = "ios" + static let flutter = "flutter" +} + diff --git a/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo.xcodeproj/project.pbxproj b/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo.xcodeproj/project.pbxproj index ba1120b..b55825c 100644 --- a/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo.xcodeproj/project.pbxproj +++ b/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ B315DE802BD27F2200EBC375 /* web3.swift in Frameworks */ = {isa = PBXBuildFile; productRef = B315DE7F2BD27F2200EBC375 /* web3.swift */; }; F04B10EC2666395100C6E51F /* Web3Auth.plist in Resources */ = {isa = PBXBuildFile; fileRef = F04B10EB2666395100C6E51F /* Web3Auth.plist */; }; F0D52218266632CB000999DF /* Web3Auth in Frameworks */ = {isa = PBXBuildFile; productRef = F0D52217266632CB000999DF /* Web3Auth */; }; + F4F722972E41B6C600E26312 /* Segment in Frameworks */ = {isa = PBXBuildFile; productRef = F4F722962E41B6C600E26312 /* Segment */; }; FAD9BC27263823A500439779 /* MainApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD9BC26263823A500439779 /* MainApp.swift */; }; FAD9BC29263823A500439779 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD9BC28263823A500439779 /* ContentView.swift */; }; FAD9BC2B263823A600439779 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FAD9BC2A263823A600439779 /* Assets.xcassets */; }; @@ -48,6 +49,7 @@ buildActionMask = 2147483647; files = ( F0D52218266632CB000999DF /* Web3Auth in Frameworks */, + F4F722972E41B6C600E26312 /* Segment in Frameworks */, B315DE7E2BD27F2200EBC375 /* web3-zksync.swift in Frameworks */, B315DE802BD27F2200EBC375 /* web3.swift in Frameworks */, 28BE82322BF32804008A2B0C /* Web3Auth in Frameworks */, @@ -148,6 +150,7 @@ B315DE7D2BD27F2200EBC375 /* web3-zksync.swift */, B315DE7F2BD27F2200EBC375 /* web3.swift */, 28BE82312BF32804008A2B0C /* Web3Auth */, + F4F722962E41B6C600E26312 /* Segment */, ); productName = Web3authSwiftSdkDemo; productReference = FAD9BC23263823A500439779 /* Web3authSwiftSdkDemo.app */; @@ -179,6 +182,7 @@ packageReferences = ( B315DE7C2BD27F2200EBC375 /* XCRemoteSwiftPackageReference "web3" */, 28BE82302BF32804008A2B0C /* XCLocalSwiftPackageReference ".." */, + F4F722952E41B6C600E26312 /* XCRemoteSwiftPackageReference "analytics-swift" */, ); productRefGroup = FAD9BC24263823A500439779 /* Products */; projectDirPath = ""; @@ -419,6 +423,14 @@ minimumVersion = 1.6.1; }; }; + F4F722952E41B6C600E26312 /* XCRemoteSwiftPackageReference "analytics-swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/segmentio/analytics-swift"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.8.0; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -440,6 +452,11 @@ isa = XCSwiftPackageProductDependency; productName = Web3Auth; }; + F4F722962E41B6C600E26312 /* Segment */ = { + isa = XCSwiftPackageProductDependency; + package = F4F722952E41B6C600E26312 /* XCRemoteSwiftPackageReference "analytics-swift" */; + productName = Segment; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = FAD9BC1B263823A500439779 /* Project object */; diff --git a/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f9b29e3..7552a82 100644 --- a/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,6 +1,15 @@ { "object": { "pins": [ + { + "package": "Segment", + "repositoryURL": "https://github.com/segmentio/analytics-swift", + "state": { + "branch": null, + "revision": "5d3a762da5412d324205a58358b95e1da334c21e", + "version": "1.8.0" + } + }, { "package": "BigInt", "repositoryURL": "https://github.com/attaswift/BigInt", @@ -37,6 +46,15 @@ "version": "2.0.2" } }, + { + "package": "JSONSafeEncoding", + "repositoryURL": "https://github.com/segmentio/jsonsafeencoding-swift.git", + "state": { + "branch": null, + "revision": "af6a8b360984085e36c6341b21ecb35c12f47ebd", + "version": "2.0.0" + } + }, { "package": "jwt-kit", "repositoryURL": "https://github.com/vapor/jwt-kit.git", @@ -82,13 +100,22 @@ "version": "6.1.0" } }, + { + "package": "Sovran", + "repositoryURL": "https://github.com/segmentio/sovran-swift.git", + "state": { + "branch": null, + "revision": "24867f3e4ac62027db9827112135e6531b6f4051", + "version": "1.1.2" + } + }, { "package": "swift-asn1", "repositoryURL": "https://github.com/apple/swift-asn1.git", "state": { "branch": null, - "revision": "a54383ada6cecde007d374f58f864e29370ba5c3", - "version": "1.3.2" + "revision": "f70225981241859eb4aa1a18a75531d26637c8cc", + "version": "1.4.0" } }, { @@ -114,8 +141,8 @@ "repositoryURL": "https://github.com/apple/swift-crypto.git", "state": { "branch": null, - "revision": "e8d6eba1fef23ae5b359c46b03f7d94be2f41fed", - "version": "3.12.3" + "revision": "176abc28e002a9952470f08745cd26fad9286776", + "version": "3.13.3" } }, {