diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fbafbbd5c..3174bdc93 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,4 +35,4 @@ jobs: APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }} WALLETAPP_SENTRY_DSN: ${{ secrets.WALLETAPP_SENTRY_DSN }} run: | - make release_all APPLE_ID=${{ secrets.APPLE_ID }} TOKEN=$(echo -n $GH_USER:$GH_TOKEN | base64) PROJECT_ID=${{ secrets.RELEASE_PROJECT_ID }} WALLETAPP_SENTRY_DSN=${{ secrets.WALLETAPP_SENTRY_DSN }} + make release_all APPLE_ID=${{ secrets.APPLE_ID }} TOKEN=$(echo -n $GH_USER:$GH_TOKEN | base64) PROJECT_ID=${{ secrets.RELEASE_PROJECT_ID }} WALLETAPP_SENTRY_DSN=${{ secrets.WALLETAPP_SENTRY_DSN }} MIXPANEL_TOKEN=${{secrets.MIXPANEL_TOKEN}} diff --git a/Configuration.xcconfig b/Configuration.xcconfig index 27c07ad29..361714e46 100644 --- a/Configuration.xcconfig +++ b/Configuration.xcconfig @@ -14,4 +14,6 @@ RELAY_HOST = relay.walletconnect.com // WALLETAPP_SENTRY_DSN = WALLETAPP_SENTRY_DSN +// MIXPANEL_TOKEN = MIXPANEL_TOKEN + CAST_HOST = notify.walletconnect.com diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index a0a35346e..f40872c6c 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -34,11 +34,13 @@ 847BD1E7298A806800076C90 /* NotificationsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847BD1E2298A806800076C90 /* NotificationsInteractor.swift */; }; 847BD1E8298A806800076C90 /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847BD1E3298A806800076C90 /* NotificationsView.swift */; }; 847BD1EB298A87AB00076C90 /* SubscriptionsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847BD1EA298A87AB00076C90 /* SubscriptionsViewModel.swift */; }; - 847CF3AF28E3141700F1D760 /* WalletConnectPush in Frameworks */ = {isa = PBXBuildFile; productRef = 847CF3AE28E3141700F1D760 /* WalletConnectPush */; settings = {ATTRIBUTES = (Required, ); }; }; 847F08012A25DBFF00B2A5A4 /* XPlatformW3WTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847F08002A25DBFF00B2A5A4 /* XPlatformW3WTests.swift */; }; 8487A9442A836C2A0003D5AF /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 8487A9432A836C2A0003D5AF /* Sentry */; }; 8487A9462A836C3F0003D5AF /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 8487A9452A836C3F0003D5AF /* Sentry */; }; 8487A9482A83AD680003D5AF /* LoggingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8487A9472A83AD680003D5AF /* LoggingService.swift */; }; + 84943C7B2A9BA206007EBAC2 /* Mixpanel in Frameworks */ = {isa = PBXBuildFile; productRef = 84943C7A2A9BA206007EBAC2 /* Mixpanel */; }; + 84943C7D2A9BA328007EBAC2 /* Mixpanel in Frameworks */ = {isa = PBXBuildFile; productRef = 84943C7C2A9BA328007EBAC2 /* Mixpanel */; }; + 84943C7F2A9BA48C007EBAC2 /* ProfilingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84943C7E2A9BA48C007EBAC2 /* ProfilingService.swift */; }; 849D7A93292E2169006A2BD4 /* NotifyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849D7A92292E2169006A2BD4 /* NotifyTests.swift */; }; 84A6E3C32A386BBC008A0571 /* Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A6E3C22A386BBC008A0571 /* Publisher.swift */; }; 84AA01DB28CF0CD7005D48D8 /* XCTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AA01DA28CF0CD7005D48D8 /* XCTest.swift */; }; @@ -389,6 +391,7 @@ 847F08002A25DBFF00B2A5A4 /* XPlatformW3WTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XPlatformW3WTests.swift; sourceTree = ""; }; 8487A92E2A7BD2F30003D5AF /* XPlatformProtocolTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = XPlatformProtocolTests.xctestplan; path = ../XPlatformProtocolTests.xctestplan; sourceTree = ""; }; 8487A9472A83AD680003D5AF /* LoggingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingService.swift; sourceTree = ""; }; + 84943C7E2A9BA48C007EBAC2 /* ProfilingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilingService.swift; sourceTree = ""; }; 849A4F18298281E300E61ACE /* WalletAppRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WalletAppRelease.entitlements; sourceTree = ""; }; 849A4F19298281F100E61ACE /* PNDecryptionServiceRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PNDecryptionServiceRelease.entitlements; sourceTree = ""; }; 849D7A92292E2169006A2BD4 /* NotifyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyTests.swift; sourceTree = ""; }; @@ -644,6 +647,7 @@ A54195A52934E83F0035AD19 /* Web3 in Frameworks */, 8487A9442A836C2A0003D5AF /* Sentry in Frameworks */, A5D85228286333E300DAF5C3 /* Starscream in Frameworks */, + 84943C7B2A9BA206007EBAC2 /* Mixpanel in Frameworks */, A573C53929EC365000E3CBFD /* HDWalletKit in Frameworks */, A5BB7FA328B6A50400707FC6 /* WalletConnectAuth in Frameworks */, ); @@ -710,6 +714,7 @@ C56EE255293F569A004840D1 /* Starscream in Frameworks */, A5B6C0F52A6EAB2800927332 /* WalletConnectNotify in Frameworks */, C56EE27B293F56F8004840D1 /* WalletConnectAuth in Frameworks */, + 84943C7D2A9BA328007EBAC2 /* Mixpanel in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1669,6 +1674,7 @@ 84DB38F22983CDAE00BFEE37 /* PushRegisterer.swift */, A51811972A52E21A00A52B15 /* ConfigurationService.swift */, 8487A9472A83AD680003D5AF /* LoggingService.swift */, + 84943C7E2A9BA48C007EBAC2 /* ProfilingService.swift */, ); path = ApplicationLayer; sourceTree = ""; @@ -1823,6 +1829,7 @@ CF25F28A2A432488009C7E49 /* WalletConnectModal */, 8487A9432A836C2A0003D5AF /* Sentry */, A5B6C0F02A6EAB0800927332 /* WalletConnectNotify */, + 84943C7A2A9BA206007EBAC2 /* Mixpanel */, ); productName = DApp; productReference = 84CE641C27981DED00142511 /* DApp.app */; @@ -1948,6 +1955,7 @@ A573C53C29EC366500E3CBFD /* HDWalletKit */, 8487A9452A836C3F0003D5AF /* Sentry */, A5B6C0F42A6EAB2800927332 /* WalletConnectNotify */, + 84943C7C2A9BA328007EBAC2 /* Mixpanel */, ); productName = ChatWallet; productReference = C56EE21B293F55ED004840D1 /* WalletApp.app */; @@ -2024,6 +2032,7 @@ A58EC60F299D57B800F3452A /* XCRemoteSwiftPackageReference "swiftui-async-button" */, A561C7FE29DF32CE00DF540D /* XCRemoteSwiftPackageReference "HDWallet" */, 8487A9422A836C2A0003D5AF /* XCRemoteSwiftPackageReference "sentry-cocoa" */, + 84943C792A9BA206007EBAC2 /* XCRemoteSwiftPackageReference "mixpanel-swift" */, ); productRefGroup = 764E1D3D26F8D3FC00A1FB15 /* Products */; projectDirPath = ""; @@ -2374,6 +2383,7 @@ C5B2F6F929705293000DBA0E /* SessionRequestPresenter.swift in Sources */, A57879712A4EDC8100F8D10B /* TextFieldView.swift in Sources */, 84DB38F32983CDAE00BFEE37 /* PushRegisterer.swift in Sources */, + 84943C7F2A9BA48C007EBAC2 /* ProfilingService.swift in Sources */, C5B2F6FB297055B0000DBA0E /* ETHSigner.swift in Sources */, C56EE274293F56D7004840D1 /* SceneViewController.swift in Sources */, 847BD1E5298A806800076C90 /* NotificationsPresenter.swift in Sources */, @@ -3105,6 +3115,14 @@ minimumVersion = 8.0.0; }; }; + 84943C792A9BA206007EBAC2 /* XCRemoteSwiftPackageReference "mixpanel-swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/mixpanel/mixpanel-swift"; + requirement = { + branch = master; + kind = branch; + }; + }; A5434021291E6A270068F706 /* XCRemoteSwiftPackageReference "solana-swift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/flypaper0/solana-swift"; @@ -3175,6 +3193,16 @@ package = 8487A9422A836C2A0003D5AF /* XCRemoteSwiftPackageReference "sentry-cocoa" */; productName = Sentry; }; + 84943C7A2A9BA206007EBAC2 /* Mixpanel */ = { + isa = XCSwiftPackageProductDependency; + package = 84943C792A9BA206007EBAC2 /* XCRemoteSwiftPackageReference "mixpanel-swift" */; + productName = Mixpanel; + }; + 84943C7C2A9BA328007EBAC2 /* Mixpanel */ = { + isa = XCSwiftPackageProductDependency; + package = 84943C792A9BA206007EBAC2 /* XCRemoteSwiftPackageReference "mixpanel-swift" */; + productName = Mixpanel; + }; 84DDB4EC28ABB663003D66ED /* WalletConnectAuth */ = { isa = XCSwiftPackageProductDependency; productName = WalletConnectAuth; diff --git a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d71b949a8..9d6fdc5ce 100644 --- a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -28,6 +28,15 @@ "version": null } }, + { + "package": "Mixpanel", + "repositoryURL": "https://github.com/mixpanel/mixpanel-swift", + "state": { + "branch": "master", + "revision": "1ce27d937009d5ecce74dad97d69898ffea49c75", + "version": null + } + }, { "package": "PromiseKit", "repositoryURL": "https://github.com/mxcl/PromiseKit.git", diff --git a/Example/IntegrationTests/Push/NotifyTests.swift b/Example/IntegrationTests/Push/NotifyTests.swift index 10dc8e061..965c56f5a 100644 --- a/Example/IntegrationTests/Push/NotifyTests.swift +++ b/Example/IntegrationTests/Push/NotifyTests.swift @@ -9,8 +9,6 @@ import WalletConnectNetworking import WalletConnectPush @testable import WalletConnectNotify @testable import WalletConnectPairing -@testable import WalletConnectSync -@testable import WalletConnectHistory import WalletConnectIdentity import WalletConnectSigner @@ -22,8 +20,6 @@ final class NotifyTests: XCTestCase { let gmDappUrl = "https://notify.gm.walletconnect.com/" - var pairingStorage: PairingStorage! - let pk = try! EthereumPrivateKey() var privateKey: Data { @@ -34,9 +30,9 @@ final class NotifyTests: XCTestCase { return Account("eip155:1:" + pk.address.hex(eip55: true))! } - private var publishers = [AnyCancellable]() + private var publishers = Set() - func makeClientDependencies(prefix: String) -> (PairingClient, NetworkInteracting, SyncClient, KeychainStorageProtocol, KeyValueStorage) { + func makeClientDependencies(prefix: String) -> (PairingClient, NetworkInteracting, KeychainStorageProtocol, KeyValueStorage) { let keychain = KeychainStorageMock() let keyValueStorage = RuntimeKeyValueStorage() @@ -64,16 +60,14 @@ final class NotifyTests: XCTestCase { keychainStorage: keychain, networkingClient: networkingClient) - let syncClient = SyncClientFactory.create(networkInteractor: networkingClient, bip44: DefaultBIP44Provider(), keychain: keychain) - let clientId = try! networkingClient.getClientId() networkingLogger.debug("My client id is: \(clientId)") - return (pairingClient, networkingClient, syncClient, keychain, keyValueStorage) + return (pairingClient, networkingClient, keychain, keyValueStorage) } func makeWalletClients() { let prefix = "🦋 Wallet: " - let (pairingClient, networkingInteractor, syncClient, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) + let (pairingClient, networkingInteractor, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) let notifyLogger = ConsoleLogger(prefix: prefix + " [Notify]", loggingLevel: .debug) walletPairingClient = pairingClient let pushClient = PushClientFactory.create(projectId: "", @@ -81,11 +75,6 @@ final class NotifyTests: XCTestCase { keychainStorage: keychain, environment: .sandbox) let keyserverURL = URL(string: "https://keys.walletconnect.com")! - let historyClient = HistoryClientFactory.create( - historyUrl: "https://history.walletconnect.com", - relayUrl: "wss://relay.walletconnect.com", - keychain: keychain - ) walletNotifyClient = NotifyClientFactory.create(keyserverURL: keyserverURL, logger: notifyLogger, keyValueStorage: keyValueStorage, @@ -94,8 +83,6 @@ final class NotifyTests: XCTestCase { networkInteractor: networkingInteractor, pairingRegisterer: pairingClient, pushClient: pushClient, - syncClient: syncClient, - historyClient: historyClient, crypto: DefaultCryptoProvider()) } @@ -106,15 +93,18 @@ final class NotifyTests: XCTestCase { func testWalletCreatesSubscription() async { let expectation = expectation(description: "expects to create notify subscription") let metadata = AppMetadata(name: "GM Dapp", description: "", url: gmDappUrl, icons: []) + + walletNotifyClient.newSubscriptionPublisher + .sink { [unowned self] subscription in + Task(priority: .high) { + try! await walletNotifyClient.deleteSubscription(topic: subscription.topic) + expectation.fulfill() + } + }.store(in: &publishers) + try! await walletNotifyClient.register(account: account, onSign: sign) try! await walletNotifyClient.subscribe(metadata: metadata, account: account, onSign: sign) - walletNotifyClient.subscriptionsPublisher - .first() - .sink { [unowned self] subscriptions in - XCTAssertNotNil(subscriptions.first) - Task { try! await walletNotifyClient.deleteSubscription(topic: subscriptions.first!.topic) } - expectation.fulfill() - }.store(in: &publishers) + wait(for: [expectation], timeout: InputConfig.defaultTimeout) } @@ -124,55 +114,57 @@ final class NotifyTests: XCTestCase { let updateScope: Set = ["alerts"] try! await walletNotifyClient.register(account: account, onSign: sign) try! await walletNotifyClient.subscribe(metadata: metadata, account: account, onSign: sign) - walletNotifyClient.subscriptionsPublisher - .first() - .sink { [unowned self] subscriptions in - sleep(1) - Task { try! await walletNotifyClient.update(topic: subscriptions.first!.topic, scope: updateScope) } + walletNotifyClient.newSubscriptionPublisher + .sink { [unowned self] subscription in + Task(priority: .high) { + try! await walletNotifyClient.update(topic: subscription.topic, scope: updateScope) + } } .store(in: &publishers) walletNotifyClient.updateSubscriptionPublisher - .sink { [unowned self] result in - guard case .success(let subscription) = result else { XCTFail(); return } + .sink { [unowned self] subscription in let updatedScope = Set(subscription.scope.filter{ $0.value.enabled == true }.keys) XCTAssertEqual(updatedScope, updateScope) - Task { try! await walletNotifyClient.deleteSubscription(topic: subscription.topic) } - expectation.fulfill() + Task(priority: .high) { + try! await walletNotifyClient.deleteSubscription(topic: subscription.topic) + expectation.fulfill() + } }.store(in: &publishers) wait(for: [expectation], timeout: InputConfig.defaultTimeout) } -// func testNotifyServerSubscribeAndNotifies() async throws { -// let subscribeExpectation = expectation(description: "creates notify subscription") -// let messageExpectation = expectation(description: "receives a notify message") -// let notifyMessage = NotifyMessage.stub() -// -// let metadata = AppMetadata(name: "GM Dapp", description: "", url: gmDappUrl, icons: []) -// try! await walletNotifyClient.register(account: account, onSign: sign) -// try! await walletNotifyClient.subscribe(metadata: metadata, account: account, onSign: sign) -// var subscription: NotifySubscription! -// walletNotifyClient.subscriptionsPublisher -// .first() -// .sink { subscriptions in -// XCTAssertNotNil(subscriptions.first) -// subscribeExpectation.fulfill() -// subscription = subscriptions.first! -// let notifier = Publisher() -// sleep(5) -// Task(priority: .high) { try await notifier.notify(topic: subscriptions.first!.topic, account: subscriptions.first!.account, message: notifyMessage) } -// }.store(in: &publishers) -// walletNotifyClient.notifyMessagePublisher -// .sink { notifyMessageRecord in -// XCTAssertEqual(notifyMessage, notifyMessageRecord.message) -// messageExpectation.fulfill() -// }.store(in: &publishers) -// -// wait(for: [subscribeExpectation, messageExpectation], timeout: InputConfig.defaultTimeout) -// try await walletNotifyClient.deleteSubscription(topic: subscription.topic) -// } + func testNotifyServerSubscribeAndNotifies() async throws { + let subscribeExpectation = expectation(description: "creates notify subscription") + let messageExpectation = expectation(description: "receives a notify message") + let notifyMessage = NotifyMessage.stub() + + let metadata = AppMetadata(name: "GM Dapp", description: "", url: gmDappUrl, icons: []) + try! await walletNotifyClient.register(account: account, onSign: sign) + try! await walletNotifyClient.subscribe(metadata: metadata, account: account, onSign: sign) + + walletNotifyClient.newSubscriptionPublisher + .sink { subscription in + let notifier = Publisher() + Task(priority: .high) { + try await notifier.notify(topic: subscription.topic, account: subscription.account, message: notifyMessage) + subscribeExpectation.fulfill() + } + }.store(in: &publishers) + + walletNotifyClient.notifyMessagePublisher + .sink { [unowned self] notifyMessageRecord in + XCTAssertEqual(notifyMessage, notifyMessageRecord.message) + Task(priority: .high) { + try await walletNotifyClient.deleteSubscription(topic: notifyMessageRecord.topic) + messageExpectation.fulfill() + } + }.store(in: &publishers) + + wait(for: [subscribeExpectation, messageExpectation], timeout: InputConfig.defaultTimeout) + } } diff --git a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift index 58054aa20..5aa30d797 100644 --- a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift +++ b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift @@ -26,9 +26,11 @@ final class ConfigurationService { crypto: DefaultCryptoProvider(), onSign: importAccount.onSign ) + Web3Inbox.instance.setLogging(level: .debug) if let clientId = try? Networking.interactor.getClientId() { LoggingService.instance.setUpUser(account: importAccount.account.absoluteString, clientId: clientId) + ProfilingService.instance.setUpProfiling(account: importAccount.account.absoluteString, clientId: clientId) } LoggingService.instance.startLogging() } diff --git a/Example/WalletApp/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift b/Example/WalletApp/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift index cea617574..1d4418f8c 100644 --- a/Example/WalletApp/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift +++ b/Example/WalletApp/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift @@ -1,19 +1,11 @@ import Foundation -import Sentry struct ThirdPartyConfigurator: Configurator { func configure() { - configureLogging() } - func configureLogging() { - guard let sentryDsn = InputConfig.sentryDsn, !sentryDsn.isEmpty else { return } - SentrySDK.start { options in - options.dsn = "https://\(sentryDsn)" - // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring. - // We recommend adjusting this value in production. - options.tracesSampleRate = 1.0 - } + private func configureLogging() { + LoggingService.instance.configure() } } diff --git a/Example/WalletApp/ApplicationLayer/LoggingService.swift b/Example/WalletApp/ApplicationLayer/LoggingService.swift index 30c0b484b..e7a74cb9b 100644 --- a/Example/WalletApp/ApplicationLayer/LoggingService.swift +++ b/Example/WalletApp/ApplicationLayer/LoggingService.swift @@ -10,7 +10,16 @@ final class LoggingService { public static var instance = LoggingService() private var publishers = [AnyCancellable]() - private var isLogging = false + private var isLogging: Bool { + get { + return queue.sync { _isLogging } + } + set { + queue.sync { _isLogging = newValue } + } + } + private var _isLogging = false + private let queue = DispatchQueue(label: "com.walletApp.loggingService") func setUpUser(account: String, clientId: String) { @@ -20,20 +29,29 @@ final class LoggingService { SentrySDK.setUser(user) } - func startLogging() { - queue.sync { - guard isLogging == false else { return } - isLogging = true + func configure() { + guard let sentryDsn = InputConfig.sentryDsn, !sentryDsn.isEmpty else { return } + SentrySDK.start { options in + options.dsn = "https://\(sentryDsn)" + options.tracesSampleRate = 1.0 } + } + + func startLogging() { + guard !isLogging else { return } + isLogging = true Networking.instance.logsPublisher - .sink { log in - self.queue.sync { + .sink { [weak self] log in + self?.queue.sync { switch log { case .error(let log): - SentrySDK.capture(error: LoggingError.networking(log)) + SentrySDK.capture(error: LoggingError.networking(log.aggregated)) case .warn(let log): - SentrySDK.capture(message: log) + // Example of setting level to warning + var event = Event(level: .warning) + event.message = SentryMessage(formatted: log.aggregated) + SentrySDK.capture(event: event) default: return } diff --git a/Example/WalletApp/ApplicationLayer/ProfilingService.swift b/Example/WalletApp/ApplicationLayer/ProfilingService.swift new file mode 100644 index 000000000..14a778061 --- /dev/null +++ b/Example/WalletApp/ApplicationLayer/ProfilingService.swift @@ -0,0 +1,58 @@ +import Foundation +import Mixpanel +import WalletConnectNetworking +import Combine +import Web3Inbox + +final class ProfilingService { + public static var instance = ProfilingService() + + private let queue = DispatchQueue(label: "com.walletApp.profilingService") + private var publishers = [AnyCancellable]() + private var isProfiling: Bool { + get { + return queue.sync { _isProfiling } + } + set { + queue.sync { _isProfiling = newValue } + } + } + private var _isProfiling = false + + func setUpProfiling(account: String, clientId: String) { + guard !isProfiling else { return } + isProfiling = true + + guard let token = InputConfig.mixpanelToken, !token.isEmpty else { return } + + Mixpanel.initialize(token: token, trackAutomaticEvents: true) + let mixpanel = Mixpanel.mainInstance() + mixpanel.alias = account + mixpanel.identify(distinctId: clientId) + mixpanel.people.set(properties: ["$name": account, "account": account]) + + handleLogs(from: Networking.instance.logsPublisher) + handleLogs(from: Web3Inbox.instance.logsPublisher) + } + + private func handleLogs(from publisher: AnyPublisher) { + publisher + .sink { [unowned self] log in + self.queue.sync { + switch log { + case .error(let logMessage), + .warn(let logMessage), + .debug(let logMessage): + self.send(logMessage: logMessage) + default: + return + } + } + } + .store(in: &publishers) + } + + func send(logMessage: LogMessage) { + Mixpanel.mainInstance().track(event: logMessage.message, properties: logMessage.properties) + } +} diff --git a/Example/WalletApp/Common/InputConfig.swift b/Example/WalletApp/Common/InputConfig.swift index cd523e551..b8ce51193 100644 --- a/Example/WalletApp/Common/InputConfig.swift +++ b/Example/WalletApp/Common/InputConfig.swift @@ -12,6 +12,10 @@ struct InputConfig { static var sentryDsn: String? { return config(for: "WALLETAPP_SENTRY_DSN") } + + static var mixpanelToken: String? { + return config(for: "MIXPANEL_TOKEN") + } private static func config(for key: String) -> String? { return Bundle.main.object(forInfoDictionaryKey: key) as? String diff --git a/Example/WalletApp/Other/Info.plist b/Example/WalletApp/Other/Info.plist index 3a0166b38..2f0f15d26 100644 --- a/Example/WalletApp/Other/Info.plist +++ b/Example/WalletApp/Other/Info.plist @@ -30,6 +30,8 @@ $(WALLETAPP_SENTRY_DSN) SIMULATOR_IDENTIFIER $(SIMULATOR_IDENTIFIER) + MIXPANEL_TOKEN + $(MIXPANEL_TOKEN) UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift index 7167284c2..4be0dc58a 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift @@ -16,6 +16,7 @@ final class MainPresenter { var viewControllers: [UIViewController] { return [ router.walletViewController(importAccount: importAccount), + router.notificationsViewController(), router.web3InboxViewController(), router.settingsViewController() ] diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift index 1c93883ad..3f2051baf 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift @@ -2,6 +2,7 @@ import UIKit enum TabPage: CaseIterable { case wallet + case notifications case web3Inbox case settings @@ -9,6 +10,8 @@ enum TabPage: CaseIterable { switch self { case .wallet: return "Apps" + case .notifications: + return "Notifications" case .web3Inbox: return "Web3Inbox" case .settings: @@ -20,6 +23,8 @@ enum TabPage: CaseIterable { switch self { case .wallet: return UIImage(systemName: "house.fill")! + case .notifications: + return UIImage(systemName: "bell.fill")! case .web3Inbox: return UIImage(systemName: "bell.fill")! case .settings: @@ -32,6 +37,6 @@ enum TabPage: CaseIterable { } static var enabledTabs: [TabPage] { - return [.wallet, .web3Inbox, .settings] + return [.wallet, .notifications, .web3Inbox, .settings] } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift index e198f8383..b1953934e 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift @@ -9,8 +9,8 @@ final class PushMessagesInteractor { self.subscription = subscription } - var notifyMessagePublisher: AnyPublisher { - return Notify.instance.notifyMessagePublisher + var messagesPublisher: AnyPublisher<[NotifyMessageRecord], Never> { + return Notify.instance.messagesPublisher(topic: subscription.topic) } func getPushMessages() -> [NotifyMessageRecord] { diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift index 1c725aec6..ec8b91c8f 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift @@ -8,19 +8,35 @@ final class PushMessagesPresenter: ObservableObject { private let router: PushMessagesRouter private var disposeBag = Set() - @Published var pushMessages: [PushMessageViewModel] = [] + @Published private var pushMessages: [NotifyMessageRecord] = [] + + var messages: [PushMessageViewModel] { + return pushMessages + .sorted { $0.publishedAt > $1.publishedAt } + .map { PushMessageViewModel(pushMessageRecord: $0) } + } init(interactor: PushMessagesInteractor, router: PushMessagesRouter) { - defer { reloadPushMessages() } + defer { setupInitialState() } self.interactor = interactor self.router = router + setUpMessagesRefresh() + } + + private func setUpMessagesRefresh() { + Timer.publish(every: 10.0, on: .main, in: .default) + .autoconnect() + .sink(receiveValue: { [weak self] _ in + guard let self = self else { return } + self.pushMessages = self.interactor.getPushMessages() + }).store(in: &disposeBag) } + func deletePushMessage(at indexSet: IndexSet) { if let index = indexSet.first { interactor.deletePushMessage(id: pushMessages[index].id) } - reloadPushMessages() } } @@ -40,27 +56,14 @@ extension PushMessagesPresenter: SceneViewModel { private extension PushMessagesPresenter { - func reloadPushMessages() { - self.pushMessages = interactor.getPushMessages() - .sorted { - // Most recent first - $0.publishedAt > $1.publishedAt - } - .map { pushMessageRecord in - PushMessageViewModel(pushMessageRecord: pushMessageRecord) - } - - interactor.notifyMessagePublisher + func setupInitialState() { + pushMessages = interactor.getPushMessages() + + interactor.messagesPublisher .receive(on: DispatchQueue.main) - .sink { [weak self] newPushMessage in - let newMessageViewModel = PushMessageViewModel(pushMessageRecord: newPushMessage) - guard let index = self?.pushMessages.firstIndex( - where: { $0.pushMessageRecord.publishedAt > newPushMessage.publishedAt } - ) else { - self?.pushMessages.append(newMessageViewModel) - return - } - self?.pushMessages.insert(newMessageViewModel, at: index) + .sink { [weak self] messages in + guard let self = self else { return } + self.pushMessages = self.interactor.getPushMessages() } .store(in: &disposeBag) } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift index c29766ed3..7f22c53f6 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift @@ -11,7 +11,7 @@ struct PushMessagesView: View { VStack(alignment: .leading, spacing: 16) { ZStack { - if presenter.pushMessages.isEmpty { + if presenter.messages.isEmpty { VStack(spacing: 10) { Image(systemName: "bell.badge.fill") .resizable() @@ -29,9 +29,9 @@ struct PushMessagesView: View { } VStack { - if !presenter.pushMessages.isEmpty { + if !presenter.messages.isEmpty { List { - ForEach(presenter.pushMessages, id: \.id) { pm in + ForEach(presenter.messages, id: \.id) { pm in notificationView(pushMessage: pm) .listRowSeparator(.hidden) .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 16, trailing: 0)) diff --git a/Makefile b/Makefile index 6d054315d..32cdfdd66 100755 --- a/Makefile +++ b/Makefile @@ -67,11 +67,11 @@ x_platform_protocol_tests: ./run_tests.sh --scheme IntegrationTests --testplan XPlatformProtocolTests --project Example/ExampleApp.xcodeproj release_wallet: - fastlane release_testflight username:$(APPLE_ID) token:$(TOKEN) relay_host:$(RELAY_HOST) project_id:$(PROJECT_ID) sentry_dsn:$(WALLETAPP_SENTRY_DSN) --env WalletApp + fastlane release_testflight username:$(APPLE_ID) token:$(TOKEN) relay_host:$(RELAY_HOST) project_id:$(PROJECT_ID) sentry_dsn:$(WALLETAPP_SENTRY_DSN) mixpanel_token:$(MIXPANEL_TOKEN) --env WalletApp release_showcase: fastlane release_testflight username:$(APPLE_ID) token:$(TOKEN) relay_host:$(RELAY_HOST) project_id:$(PROJECT_ID) --env Showcase release_all: - fastlane release_testflight username:$(APPLE_ID) token:$(TOKEN) relay_host:$(RELAY_HOST) project_id:$(PROJECT_ID) sentry_dsn:$(WALLETAPP_SENTRY_DSN) --env WalletApp + fastlane release_testflight username:$(APPLE_ID) token:$(TOKEN) relay_host:$(RELAY_HOST) project_id:$(PROJECT_ID) sentry_dsn:$(WALLETAPP_SENTRY_DSN) mixpanel_token:$(MIXPANEL_TOKEN) --env WalletApp fastlane release_testflight username:$(APPLE_ID) token:$(TOKEN) relay_host:$(RELAY_HOST) project_id:$(PROJECT_ID) --env Showcase diff --git a/Package.swift b/Package.swift index dc111f0dd..8815d2a74 100644 --- a/Package.swift +++ b/Package.swift @@ -77,7 +77,7 @@ let package = Package( path: "Sources/Web3Wallet"), .target( name: "WalletConnectNotify", - dependencies: ["WalletConnectPairing", "WalletConnectPush", "WalletConnectIdentity", "WalletConnectSync", "WalletConnectHistory"], + dependencies: ["WalletConnectPairing", "WalletConnectPush", "WalletConnectIdentity", "WalletConnectSigner"], path: "Sources/WalletConnectNotify"), .target( name: "WalletConnectPush", diff --git a/Sources/WalletConnectIdentity/IdentityClient.swift b/Sources/WalletConnectIdentity/IdentityClient.swift index 8ec6d2943..57335fb79 100644 --- a/Sources/WalletConnectIdentity/IdentityClient.swift +++ b/Sources/WalletConnectIdentity/IdentityClient.swift @@ -72,4 +72,9 @@ public final class IdentityClient { public func getInviteKey(for account: Account) throws -> AgreementPublicKey { return try identityStorage.getInviteKey(for: account) } + + public func isIdentityRegistered(account: Account) -> Bool { + let key = try? identityStorage.getIdentityKey(for: account) + return key != nil + } } diff --git a/Sources/WalletConnectNetworking/NetworkingInteractor.swift b/Sources/WalletConnectNetworking/NetworkingInteractor.swift index a07f0b81b..d216f9974 100644 --- a/Sources/WalletConnectNetworking/NetworkingInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkingInteractor.swift @@ -22,6 +22,7 @@ public class NetworkingInteractor: NetworkInteracting { public var logsPublisher: AnyPublisher { logger.logsPublisher .merge(with: serializer.logsPublisher) + .merge(with: relayClient.logsPublisher) .eraseToAnyPublisher() } diff --git a/Sources/WalletConnectNotify/Client/Common/DeleteNotifySubscriptionService.swift b/Sources/WalletConnectNotify/Client/Common/DeleteNotifySubscriptionService.swift index cef34a689..fc4a361fd 100644 --- a/Sources/WalletConnectNotify/Client/Common/DeleteNotifySubscriptionService.swift +++ b/Sources/WalletConnectNotify/Client/Common/DeleteNotifySubscriptionService.swift @@ -36,9 +36,6 @@ class DeleteNotifySubscriptionService { guard let subscription = notifyStorage.getSubscription(topic: topic) else { throw Errors.notifySubscriptionNotFound} - try await notifyStorage.deleteSubscription(topic: topic) - notifyStorage.deleteMessages(topic: topic) - let protocolMethod = NotifyDeleteProtocolMethod() let dappPubKey = try await webDidResolver.resolvePublicKey(dappUrl: subscription.metadata.url) @@ -52,7 +49,8 @@ class DeleteNotifySubscriptionService { let request = RPCRequest(method: protocolMethod.method, params: wrapper) try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) - try await notifyStorage.deleteSubscription(topic: topic) + try notifyStorage.deleteSubscription(topic: topic) + notifyStorage.deleteMessages(topic: topic) networkingInteractor.unsubscribe(topic: topic) diff --git a/Sources/WalletConnectNotify/Client/Common/NotifyResubscribeService.swift b/Sources/WalletConnectNotify/Client/Common/NotifyResubscribeService.swift index 4d48f7a7c..80fbd12ec 100644 --- a/Sources/WalletConnectNotify/Client/Common/NotifyResubscribeService.swift +++ b/Sources/WalletConnectNotify/Client/Common/NotifyResubscribeService.swift @@ -4,13 +4,15 @@ import Combine final class NotifyResubscribeService { private var publishers = Set() + private let logger: ConsoleLogging private let networkInteractor: NetworkInteracting private let notifyStorage: NotifyStorage - init(networkInteractor: NetworkInteracting, notifyStorage: NotifyStorage) { + init(networkInteractor: NetworkInteracting, notifyStorage: NotifyStorage, logger: ConsoleLogging) { self.networkInteractor = networkInteractor self.notifyStorage = notifyStorage + self.logger = logger setUpResubscription() } @@ -19,6 +21,7 @@ final class NotifyResubscribeService { .sink { [unowned self] status in guard status == .connected else { return } let topics = notifyStorage.getSubscriptions().map{$0.topic} + logger.debug("Resubscribing to notify subscription topics: \(topics)", properties: ["topics": topics.joined(separator: ", ")]) Task(priority: .high) { try await networkInteractor.batchSubscribe(topics: topics) } diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift index 00f499a57..8065dfa78 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift @@ -23,11 +23,16 @@ public class NotifyClient { } public var notifyMessagePublisher: AnyPublisher { - notifyMessageSubscriber.notifyMessagePublisher + return notifyMessageSubscriber.notifyMessagePublisher } - public var updateSubscriptionPublisher: AnyPublisher, Never> { - return notifyUpdateResponseSubscriber.updateSubscriptionPublisher + public var updateSubscriptionPublisher: AnyPublisher { + return notifyStorage.updateSubscriptionPublisher + } + + public var logsPublisher: AnyPublisher { + logger.logsPublisher + .eraseToAnyPublisher() } private let deleteNotifySubscriptionService: DeleteNotifySubscriptionService @@ -36,8 +41,8 @@ public class NotifyClient { public let logger: ConsoleLogging private let pushClient: PushClient + private let identityClient: IdentityClient private let notifyStorage: NotifyStorage - private let notifySyncService: NotifySyncService private let notifyMessageSubscriber: NotifyMessageSubscriber private let resubscribeService: NotifyResubscribeService private let notifySubscribeResponseSubscriber: NotifySubscribeResponseSubscriber @@ -48,10 +53,10 @@ public class NotifyClient { init(logger: ConsoleLogging, kms: KeyManagementServiceProtocol, + identityClient: IdentityClient, pushClient: PushClient, notifyMessageSubscriber: NotifyMessageSubscriber, notifyStorage: NotifyStorage, - notifySyncService: NotifySyncService, deleteNotifySubscriptionService: DeleteNotifySubscriptionService, resubscribeService: NotifyResubscribeService, notifySubscribeRequester: NotifySubscribeRequester, @@ -63,9 +68,9 @@ public class NotifyClient { ) { self.logger = logger self.pushClient = pushClient + self.identityClient = identityClient self.notifyMessageSubscriber = notifyMessageSubscriber self.notifyStorage = notifyStorage - self.notifySyncService = notifySyncService self.deleteNotifySubscriptionService = deleteNotifySubscriptionService self.resubscribeService = resubscribeService self.notifySubscribeRequester = notifySubscribeRequester @@ -77,11 +82,11 @@ public class NotifyClient { } public func register(account: Account, onSign: @escaping SigningCallback) async throws { - try await notifySyncService.registerSyncIfNeeded(account: account, onSign: onSign) - try await notifySyncService.registerIdentity(account: account, onSign: onSign) - try await notifyStorage.initialize(account: account) - try await notifyStorage.subscribe(account: account) - try await notifySyncService.fetchHistoryIfNeeded(account: account) + _ = try await identityClient.register(account: account, onSign: onSign) + } + + public func setLogging(level: LoggingLevel) { + logger.setLogging(level: level) } public func subscribe(metadata: AppMetadata, account: Account, onSign: @escaping SigningCallback) async throws { @@ -111,6 +116,14 @@ public class NotifyClient { public func register(deviceToken: Data) async throws { try await pushClient.register(deviceToken: deviceToken) } + + public func isIdentityRegistered(account: Account) -> Bool { + return identityClient.isIdentityRegistered(account: account) + } + + public func messagesPublisher(topic: String) -> AnyPublisher<[NotifyMessageRecord], Never> { + return notifyStorage.messagesPublisher(topic: topic) + } } #if targetEnvironment(simulator) diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift index 2e15e4266..cb77bce9a 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift @@ -2,7 +2,7 @@ import Foundation public struct NotifyClientFactory { - public static func create(networkInteractor: NetworkInteracting, pairingRegisterer: PairingRegisterer, pushClient: PushClient, syncClient: SyncClient, historyClient: HistoryClient, crypto: CryptoProvider) -> NotifyClient { + public static func create(networkInteractor: NetworkInteracting, pairingRegisterer: PairingRegisterer, pushClient: PushClient, crypto: CryptoProvider) -> NotifyClient { let logger = ConsoleLogger(prefix: "🔔",loggingLevel: .debug) let keyValueStorage = UserDefaults.standard let keyserverURL = URL(string: "https://keys.walletconnect.com")! @@ -18,8 +18,6 @@ public struct NotifyClientFactory { networkInteractor: networkInteractor, pairingRegisterer: pairingRegisterer, pushClient: pushClient, - syncClient: syncClient, - historyClient: historyClient, crypto: crypto ) } @@ -33,22 +31,17 @@ public struct NotifyClientFactory { networkInteractor: NetworkInteracting, pairingRegisterer: PairingRegisterer, pushClient: PushClient, - syncClient: SyncClient, - historyClient: HistoryClient, crypto: CryptoProvider ) -> NotifyClient { let kms = KeyManagementService(keychain: keychainStorage) - let subscriptionStore: SyncStore = SyncStoreFactory.create(name: NotifyStorageIdntifiers.notifySubscription, syncClient: syncClient, storage: keyValueStorage) - let subscriptionStoreDelegate = NotifySubscriptionStoreDelegate(networkingInteractor: networkInteractor, kms: kms, groupKeychainStorage: groupKeychainStorage) + let subscriptionStore = KeyedDatabase(storage: keyValueStorage, identifier: NotifyStorageIdntifiers.notifySubscription) let messagesStore = KeyedDatabase(storage: keyValueStorage, identifier: NotifyStorageIdntifiers.notifyMessagesRecords) - let notifyStorage = NotifyStorage(subscriptionStore: subscriptionStore, messagesStore: messagesStore, subscriptionStoreDelegate: subscriptionStoreDelegate) - let coldStartStore = CodableStore(defaults: keyValueStorage, identifier: NotifyStorageIdntifiers.coldStartStore) + let notifyStorage = NotifyStorage(subscriptionStore: subscriptionStore, messagesStore: messagesStore) let identityClient = IdentityClientFactory.create(keyserver: keyserverURL, keychain: keychainStorage, logger: logger) - let notifySyncService = NotifySyncService(syncClient: syncClient, logger: logger, historyClient: historyClient, identityClient: identityClient, subscriptionsStore: subscriptionStore, messagesStore: messagesStore, networkingInteractor: networkInteractor, kms: kms, coldStartStore: coldStartStore, groupKeychainStorage: groupKeychainStorage) let notifyMessageSubscriber = NotifyMessageSubscriber(keyserver: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, notifyStorage: notifyStorage, crypto: crypto, logger: logger) let webDidResolver = WebDidResolver() let deleteNotifySubscriptionService = DeleteNotifySubscriptionService(keyserver: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, webDidResolver: webDidResolver, kms: kms, logger: logger, notifyStorage: notifyStorage) - let resubscribeService = NotifyResubscribeService(networkInteractor: networkInteractor, notifyStorage: notifyStorage) + let resubscribeService = NotifyResubscribeService(networkInteractor: networkInteractor, notifyStorage: notifyStorage, logger: logger) let dappsMetadataStore = CodableStore(defaults: keyValueStorage, identifier: NotifyStorageIdntifiers.dappsMetadataStore) let subscriptionScopeProvider = SubscriptionScopeProvider() @@ -68,10 +61,10 @@ public struct NotifyClientFactory { return NotifyClient( logger: logger, kms: kms, + identityClient: identityClient, pushClient: pushClient, notifyMessageSubscriber: notifyMessageSubscriber, notifyStorage: notifyStorage, - notifySyncService: notifySyncService, deleteNotifySubscriptionService: deleteNotifySubscriptionService, resubscribeService: resubscribeService, notifySubscribeRequester: notifySubscribeRequester, diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift index 23867f8f3..ebc55c784 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift @@ -12,21 +12,21 @@ final class NotifyStorage: NotifyStoring { private var publishers = Set() - private let subscriptionStore: SyncStore + private let subscriptionStore: KeyedDatabase private let messagesStore: KeyedDatabase private let newSubscriptionSubject = PassthroughSubject() private let updateSubscriptionSubject = PassthroughSubject() private let deleteSubscriptionSubject = PassthroughSubject() - - private let subscriptionStoreDelegate: NotifySubscriptionStoreDelegate + private let subscriptionsSubject = PassthroughSubject<[NotifySubscription], Never>() + private let messagesSubject = PassthroughSubject<[NotifyMessageRecord], Never>() var newSubscriptionPublisher: AnyPublisher { return newSubscriptionSubject.eraseToAnyPublisher() } var updateSubscriptionPublisher: AnyPublisher { - return newSubscriptionSubject.eraseToAnyPublisher() + return updateSubscriptionSubject.eraseToAnyPublisher() } var deleteSubscriptionPublisher: AnyPublisher { @@ -34,29 +34,18 @@ final class NotifyStorage: NotifyStoring { } var subscriptionsPublisher: AnyPublisher<[NotifySubscription], Never> { - return subscriptionStore.dataUpdatePublisher + return subscriptionsSubject.eraseToAnyPublisher() } - init( - subscriptionStore: SyncStore, - messagesStore: KeyedDatabase, - subscriptionStoreDelegate: NotifySubscriptionStoreDelegate - ) { - self.subscriptionStore = subscriptionStore - self.messagesStore = messagesStore - self.subscriptionStoreDelegate = subscriptionStoreDelegate - setupSubscriptions() + var messagesPublisher: AnyPublisher<[NotifyMessageRecord], Never> { + return messagesSubject.eraseToAnyPublisher() } - // MARK: Configuration - - func initialize(account: Account) async throws { - try await subscriptionStore.create(for: account) - try subscriptionStore.setupDatabaseSubscriptions(account: account) - } + init(subscriptionStore: KeyedDatabase, messagesStore: KeyedDatabase) { + self.subscriptionStore = subscriptionStore + self.messagesStore = messagesStore - func subscribe(account: Account) async throws { - try await subscriptionStore.subscribe(for: account) + setupSubscriptions() } // MARK: Subscriptions @@ -66,28 +55,37 @@ final class NotifyStorage: NotifyStoring { } func getSubscription(topic: String) -> NotifySubscription? { - return subscriptionStore.get(for: topic) + return subscriptionStore.getAll().first(where: { $0.topic == topic }) } - func setSubscription(_ subscription: NotifySubscription) async throws { - try await subscriptionStore.set(object: subscription, for: subscription.account) + func setSubscription(_ subscription: NotifySubscription) { + subscriptionStore.set(element: subscription, for: subscription.account.absoluteString) newSubscriptionSubject.send(subscription) } - func deleteSubscription(topic: String) async throws { - try await subscriptionStore.delete(id: topic) + func deleteSubscription(topic: String) throws { + guard let subscription = getSubscription(topic: topic) else { + throw Errors.subscriptionNotFound + } + subscriptionStore.delete(id: topic, for: subscription.account.absoluteString) deleteSubscriptionSubject.send(topic) } - func updateSubscription(_ subscription: NotifySubscription, scope: [String: ScopeValue], expiry: UInt64) async throws { + func updateSubscription(_ subscription: NotifySubscription, scope: [String: ScopeValue], expiry: UInt64) { let expiry = Date(timeIntervalSince1970: TimeInterval(expiry)) let updated = NotifySubscription(topic: subscription.topic, account: subscription.account, relay: subscription.relay, metadata: subscription.metadata, scope: scope, expiry: expiry, symKey: subscription.symKey) - try await setSubscription(updated) + subscriptionStore.set(element: updated, for: updated.account.absoluteString) updateSubscriptionSubject.send(updated) } // MARK: Messages + func messagesPublisher(topic: String) -> AnyPublisher<[NotifyMessageRecord], Never> { + return messagesPublisher + .map { $0.filter { $0.topic == topic } } + .eraseToAnyPublisher() + } + func getMessages(topic: String) -> [NotifyMessageRecord] { return messagesStore.getAll(for: topic) .sorted{$0.publishedAt > $1.publishedAt} @@ -109,18 +107,17 @@ final class NotifyStorage: NotifyStoring { private extension NotifyStorage { + enum Errors: Error { + case subscriptionNotFound + } + func setupSubscriptions() { - subscriptionStore.syncUpdatePublisher.sink { [unowned self] (_, _, update) in - switch update { - case .set(let subscription): - subscriptionStoreDelegate.onUpdate(subscription) - newSubscriptionSubject.send(subscription) - case .delete(let object): - subscriptionStoreDelegate.onDelete(object, notifyStorage: self) - deleteSubscriptionSubject.send(object.topic) - case .update(let subscription): - newSubscriptionSubject.send(subscription) - } - }.store(in: &publishers) + messagesStore.onUpdate = { [unowned self] in + messagesSubject.send(messagesStore.getAll()) + } + + subscriptionStore.onUpdate = { [unowned self] in + subscriptionsSubject.send(subscriptionStore.getAll()) + } } } diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifySubscriptionStoreDelegate.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifySubscriptionStoreDelegate.swift deleted file mode 100644 index 77a2c1a4d..000000000 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifySubscriptionStoreDelegate.swift +++ /dev/null @@ -1,32 +0,0 @@ -import Foundation - -final class NotifySubscriptionStoreDelegate { - - private let networkingInteractor: NetworkInteracting - private let kms: KeyManagementServiceProtocol - private let groupKeychainStorage: KeychainStorageProtocol - - init(networkingInteractor: NetworkInteracting, kms: KeyManagementServiceProtocol, groupKeychainStorage: KeychainStorageProtocol) { - self.networkingInteractor = networkingInteractor - self.kms = kms - self.groupKeychainStorage = groupKeychainStorage - } - - func onUpdate(_ subscription: NotifySubscription) { - Task(priority: .high) { - let symmetricKey = try SymmetricKey(hex: subscription.symKey) - try kms.setSymmetricKey(symmetricKey, for: subscription.topic) - try groupKeychainStorage.add(symmetricKey, forKey: subscription.topic) - try await networkingInteractor.subscribe(topic: subscription.topic) - } - } - - func onDelete(_ subscription: NotifySubscription, notifyStorage: NotifyStorage) { - Task(priority: .high) { - kms.deleteSymmetricKey(for: subscription.topic) - try? groupKeychainStorage.delete(key: subscription.topic) - networkingInteractor.unsubscribe(topic: subscription.topic) - notifyStorage.deleteMessages(topic: subscription.topic) - } - } -} diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifySyncService.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifySyncService.swift deleted file mode 100644 index 517b81e50..000000000 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifySyncService.swift +++ /dev/null @@ -1,140 +0,0 @@ -import Foundation - -final class NotifySyncService { - - private let syncClient: SyncClient - private let historyClient: HistoryClient - private let identityClient: IdentityClient - private let logger: ConsoleLogging - private let subscriptionsStore: SyncStore - private let messagesStore: KeyedDatabase - private let networkingInteractor: NetworkInteracting - private let kms: KeyManagementServiceProtocol - private let coldStartStore: CodableStore - private let groupKeychainStorage: KeychainStorageProtocol - - init( - syncClient: SyncClient, - logger: ConsoleLogging, - historyClient: HistoryClient, - identityClient: IdentityClient, - subscriptionsStore: SyncStore, - messagesStore: KeyedDatabase, - networkingInteractor: NetworkInteracting, - kms: KeyManagementServiceProtocol, - coldStartStore: CodableStore, - groupKeychainStorage: KeychainStorageProtocol - ) { - self.syncClient = syncClient - self.logger = logger - self.historyClient = historyClient - self.identityClient = identityClient - self.subscriptionsStore = subscriptionsStore - self.messagesStore = messagesStore - self.networkingInteractor = networkingInteractor - self.kms = kms - self.coldStartStore = coldStartStore - self.groupKeychainStorage = groupKeychainStorage - } - - func registerIdentity(account: Account, onSign: @escaping SigningCallback) async throws { - _ = try await identityClient.register(account: account, onSign: onSign) - } - - func registerSyncIfNeeded(account: Account, onSign: @escaping SigningCallback) async throws { - guard !syncClient.isRegistered(account: account) else { return } - - let result = await onSign(syncClient.getMessage(account: account)) - - switch result { - case .signed(let signature): - try await syncClient.register(account: account, signature: signature) - logger.debug("Sync notifySubscriptions store registered and initialized") - case .rejected: - throw NotifyError.registerSignatureRejected - } - } - - func fetchHistoryIfNeeded(account: Account) async throws { - guard try isColdStart(account: account) else { return } - - try await historyClient.register(tags: [ - "5000", // sync_set - "5002", // sync_delete - "4002" // notify_message - ]) - - let syncTopic = try subscriptionsStore.getStoreTopic(account: account) - - let updates: [StoreSetDelete] = try await historyClient.getMessages( - topic: syncTopic, - count: 200, - direction: .backward - ) - - let inserts: [NotifySubscription] = updates.compactMap { update in - guard let value = update.value else { return nil } - return try? JSONDecoder().decode(NotifySubscription.self, from: Data(value.utf8)) - } - - let deletions: [String] = updates.compactMap { update in - guard update.value == nil else { return nil } - return update.key - } - - let subscriptions = inserts.filter { !deletions.contains( $0.databaseId ) } - - logger.debug("Received object from history: \(subscriptions)") - - try subscriptionsStore.replaceInStore(objects: subscriptions, for: account) - - for subscription in subscriptions { - let symmetricKey = try SymmetricKey(hex: subscription.symKey) - try kms.setSymmetricKey(symmetricKey, for: subscription.topic) - try groupKeychainStorage.add(symmetricKey, forKey: subscription.topic) - try await networkingInteractor.subscribe(topic: subscription.topic) - - let historyRecords: [HistoryRecord] = try await historyClient.getRecords( - topic: subscription.topic, - count: 200, - direction: .backward - ) - - let messageRecords = historyRecords.compactMap { record in - guard - let (messagePayload, _) = try? NotifyMessagePayload.decodeAndVerify(from: record.object) - else { fatalError() /* TODO: Handle error */ } - - return NotifyMessageRecord( - id: record.id.string, - topic: subscription.topic, - message: messagePayload.message, - publishedAt: Date() - ) - } - - messagesStore.set(elements: messageRecords, for: subscription.topic) - } - - coldStartStore.set(Date(), forKey: account.absoluteString) - } -} - -private extension NotifySyncService { - - struct StoreSetDelete: Codable, Equatable { - let key: String - let value: String? - } - - func isColdStart(account: Account) throws -> Bool { - guard let lastFetch = try coldStartStore.get(key: account.absoluteString) else { - return true - } - guard let days = Calendar.current.dateComponents([.day], from: lastFetch, to: Date()).day else { - return true - } - - return days >= 30 - } -} diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift index 727751621..957c4df60 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift @@ -30,10 +30,11 @@ class NotifyMessageSubscriber { networkingInteractor.requestSubscription(on: protocolMethod) .sink { [unowned self] (payload: RequestSubscriptionPayload) in - logger.debug("Received Notify Message") + logger.debug("Received Notify Message on topic: \(payload.topic)", properties: ["topic": payload.topic]) Task(priority: .high) { let (messagePayload, claims) = try NotifyMessagePayload.decodeAndVerify(from: payload.request) + logger.debug("Decoded Notify Message: \(payload.topic)", properties: ["topic": payload.topic, "messageBody": messagePayload.message.body, "messageTitle": messagePayload.message.title, "publishedAt": payload.publishedAt.description, "id": payload.id.string]) let dappPubKey = try DIDKey(did: claims.iss) let messageData = try JSONEncoder().encode(messagePayload.message) @@ -60,7 +61,7 @@ class NotifyMessageSubscriber { protocolMethod: NotifyMessageProtocolMethod() ) - logger.debug("Sent Notify Receipt Response") + logger.debug("Sent Notify Message Response on topic: \(payload.topic)", properties: ["topic" : payload.topic, "messageBody": messagePayload.message.body, "messageTitle": messagePayload.message.title, "id": payload.id.string, "result": wrapper.jwtString]) } }.store(in: &publishers) diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift index 0b84e961d..ce74dea16 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift @@ -7,10 +7,6 @@ class NotifyUpdateResponseSubscriber { private let logger: ConsoleLogging private let notifyStorage: NotifyStorage private let subscriptionScopeProvider: SubscriptionScopeProvider - private var subscriptionPublisherSubject = PassthroughSubject, Never>() - var updateSubscriptionPublisher: AnyPublisher, Never> { - return subscriptionPublisherSubject.eraseToAnyPublisher() - } init(networkingInteractor: NetworkInteracting, logger: ConsoleLogging, @@ -49,16 +45,10 @@ private extension NotifyUpdateResponseSubscriber { guard let oldSubscription = notifyStorage.getSubscription(topic: subscriptionTopic) else { logger.debug("NotifyUpdateResponseSubscriber Subscription does not exist") - subscriptionPublisherSubject.send(.failure(Errors.subscriptionDoesNotExist)) return } - let expiry = Date(timeIntervalSince1970: TimeInterval(requestClaims.exp)) - - let updatedSubscription = NotifySubscription(topic: subscriptionTopic, account: oldSubscription.account, relay: oldSubscription.relay, metadata: oldSubscription.metadata, scope: scope, expiry: expiry, symKey: oldSubscription.symKey) - - try await notifyStorage.setSubscription(updatedSubscription) - subscriptionPublisherSubject.send(.success(updatedSubscription)) + notifyStorage.updateSubscription(oldSubscription, scope: scope, expiry: requestClaims.exp) logger.debug("Updated Subscription") } diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeResponseSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeResponseSubscriber.swift index 48d32401b..b663aaa14 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeResponseSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeResponseSubscriber.swift @@ -44,14 +44,14 @@ class NotifySubscribeResponseSubscriber { networkingInteractor.responseSubscription(on: protocolMethod) .sink { [unowned self] (payload: ResponseSubscriptionPayload) in Task(priority: .high) { - logger.debug("NotifySubscribeResponseSubscriber: Received Notify Subscribe response") + logger.debug("Received Notify Subscribe response") guard let (responsePayload, _) = try? NotifySubscriptionResponsePayload.decodeAndVerify(from: payload.response) else { fatalError() /* TODO: Handle error */ } guard let responseKeys = kms.getAgreementSecret(for: payload.topic) else { - logger.debug("NotifySubscribeResponseSubscriber: no symmetric key for topic \(payload.topic)") + logger.debug("No symmetric key for topic \(payload.topic)") return subscriptionErrorSubject.send(Errors.couldNotCreateSubscription) } @@ -77,15 +77,15 @@ class NotifySubscribeResponseSubscriber { metadata = try dappsMetadataStore.get(key: payload.topic) let availableTypes = try await subscriptionScopeProvider.getSubscriptionScope(dappUrl: metadata!.url) subscribedTypes = availableTypes.filter{subscribedScope.contains($0.name)} - logger.debug("NotifySubscribeResponseSubscriber: subscribing notify subscription topic: \(notifySubscriptionTopic!)") + logger.debug("subscribing notify subscription topic: \(notifySubscriptionTopic!)") try await networkingInteractor.subscribe(topic: notifySubscriptionTopic) } catch { - logger.debug("NotifySubscribeResponseSubscriber: error: \(error)") + logger.debug("error: \(error)") return subscriptionErrorSubject.send(Errors.couldNotCreateSubscription) } guard let metadata = metadata else { - logger.debug("NotifySubscribeResponseSubscriber: no metadata for topic: \(notifySubscriptionTopic!)") + logger.debug("No metadata for topic: \(notifySubscriptionTopic!)") return subscriptionErrorSubject.send(Errors.couldNotCreateSubscription) } dappsMetadataStore.delete(forKey: payload.topic) @@ -93,9 +93,9 @@ class NotifySubscribeResponseSubscriber { let scope: [String: ScopeValue] = subscribedTypes.reduce(into: [:]) { $0[$1.name] = ScopeValue(description: $1.description, enabled: true) } let notifySubscription = NotifySubscription(topic: notifySubscriptionTopic, account: account, relay: RelayProtocolOptions(protocol: "irn", data: nil), metadata: metadata, scope: scope, expiry: expiry, symKey: agreementKeysP.sharedKey.hexRepresentation) - try await notifyStorage.setSubscription(notifySubscription) + notifyStorage.setSubscription(notifySubscription) - logger.debug("NotifySubscribeResponseSubscriber: unsubscribing response topic: \(payload.topic)") + logger.debug("Unsubscribing response topic: \(payload.topic)") networkingInteractor.unsubscribe(topic: payload.topic) } }.store(in: &publishers) diff --git a/Sources/WalletConnectNotify/Notify.swift b/Sources/WalletConnectNotify/Notify.swift index 129291010..3a8a0bb14 100644 --- a/Sources/WalletConnectNotify/Notify.swift +++ b/Sources/WalletConnectNotify/Notify.swift @@ -10,8 +10,6 @@ public class Notify { networkInteractor: Networking.interactor, pairingRegisterer: Pair.registerer, pushClient: Push.instance, - syncClient: Sync.instance, - historyClient: History.instance, crypto: config.crypto ) }() diff --git a/Sources/WalletConnectNotify/NotifyImports.swift b/Sources/WalletConnectNotify/NotifyImports.swift index 1c0a1db16..74fcfa250 100644 --- a/Sources/WalletConnectNotify/NotifyImports.swift +++ b/Sources/WalletConnectNotify/NotifyImports.swift @@ -2,6 +2,5 @@ @_exported import WalletConnectPairing @_exported import WalletConnectPush @_exported import WalletConnectIdentity -@_exported import WalletConnectSync -@_exported import WalletConnectHistory +@_exported import WalletConnectSigner #endif diff --git a/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json index 86f515d51..b0a94210e 100644 --- a/Sources/WalletConnectRelay/PackageConfig.json +++ b/Sources/WalletConnectRelay/PackageConfig.json @@ -1 +1 @@ -{"version": "1.7.0"} +{"version": "1.7.1"} diff --git a/Sources/WalletConnectRelay/RelayClient.swift b/Sources/WalletConnectRelay/RelayClient.swift index 60eb6aa06..441f40314 100644 --- a/Sources/WalletConnectRelay/RelayClient.swift +++ b/Sources/WalletConnectRelay/RelayClient.swift @@ -42,6 +42,11 @@ public final class RelayClient { private let concurrentQueue = DispatchQueue(label: "com.walletconnect.sdk.relay_client", attributes: .concurrent) + public var logsPublisher: AnyPublisher { + logger.logsPublisher + .eraseToAnyPublisher() + } + // MARK: - Initialization init( diff --git a/Sources/WalletConnectUtils/Logger/ConsoleLogger.swift b/Sources/WalletConnectUtils/Logger/ConsoleLogger.swift index 8c8b1a736..f87f2dfef 100644 --- a/Sources/WalletConnectUtils/Logger/ConsoleLogger.swift +++ b/Sources/WalletConnectUtils/Logger/ConsoleLogger.swift @@ -3,7 +3,7 @@ import Combine public protocol ConsoleLogging { var logsPublisher: AnyPublisher { get } - func debug(_ items: Any..., file: String, function: String, line: Int) + func debug(_ items: Any..., file: String, function: String, line: Int, properties: [String: String]?) func info(_ items: Any..., file: String, function: String, line: Int) func warn(_ items: Any..., file: String, function: String, line: Int) func error(_ items: Any..., file: String, function: String, line: Int) @@ -11,8 +11,8 @@ public protocol ConsoleLogging { } public extension ConsoleLogging { - func debug(_ items: Any..., file: String = #file, function: String = #function, line: Int = #line) { - debug(items, file: file, function: function, line: line) + func debug(_ items: Any..., file: String = #file, function: String = #function, line: Int = #line, properties: [String: String]? = nil) { + debug(items, file: file, function: function, line: line, properties: properties) } func info(_ items: Any..., file: String = #file, function: String = #function, line: Int = #line) { info(items, file: file, function: function, line: line) @@ -42,29 +42,32 @@ public class ConsoleLogger { self.loggingLevel = loggingLevel } - private func logMessage(_ items: Any..., logType: LoggingLevel, file: String = #file, function: String = #function, line: Int = #line) { + private func logMessage(_ items: Any..., logType: LoggingLevel, file: String = #file, function: String = #function, line: Int = #line, properties: [String: String]? = nil) { let fileName = (file as NSString).lastPathComponent items.forEach { - var log = "[\(fileName)]: \($0) - \(function) - line: \(line) - \(logFormattedDate(Date()))" - + var logMessage = "\($0)" + var properties = properties ?? [String: String]() + properties["fileName"] = fileName + properties["line"] = "\(line)" + properties["function"] = function switch logType { case .debug: - log = "\(prefix) \(log)" - logsPublisherSubject.send(.debug(log)) + logMessage = "\(prefix) \(logMessage)" + logsPublisherSubject.send(.debug(LogMessage(message: logMessage, properties: properties))) case .info: - log = "\(prefix) ℹ️ \(log)" - logsPublisherSubject.send(.info(log)) + logMessage = "\(prefix) ℹ️ \(logMessage)" + logsPublisherSubject.send(.info(LogMessage(message: logMessage, properties: properties))) case .warn: - log = "\(prefix) ⚠️ \(log)" - logsPublisherSubject.send(.warn(log)) + logMessage = "\(prefix) ⚠️ \(logMessage)" + logsPublisherSubject.send(.warn(LogMessage(message: logMessage, properties: properties))) case .error: - log = "\(prefix) ‼️ \(log)" - logsPublisherSubject.send(.error(log)) + logMessage = "\(prefix) ‼️ \(logMessage)" + logsPublisherSubject.send(.error(LogMessage(message: logMessage, properties: properties))) case .off: return } - - Swift.print(log) + logMessage = "\(prefix) [\(fileName)]: \($0) - \(function) - line: \(line) - \(logFormattedDate(Date()))" + Swift.print(logMessage) } } @@ -77,9 +80,9 @@ public class ConsoleLogger { extension ConsoleLogger: ConsoleLogging { - public func debug(_ items: Any..., file: String, function: String, line: Int) { + public func debug(_ items: Any..., file: String, function: String, line: Int, properties: [String : String]?) { if loggingLevel >= .debug { - logMessage(items, logType: .debug, file: file, function: function, line: line) + logMessage(items, logType: .debug, file: file, function: function, line: line, properties: properties) } } @@ -112,7 +115,7 @@ public struct ConsoleLoggerMock: ConsoleLogging { public init() {} - public func debug(_ items: Any..., file: String, function: String, line: Int) { } + public func debug(_ items: Any..., file: String, function: String, line: Int, properties: [String: String]?) { } public func info(_ items: Any..., file: String, function: String, line: Int) { } public func warn(_ items: Any..., file: String, function: String, line: Int) { } public func error(_ items: Any..., file: String, function: String, line: Int) { } diff --git a/Sources/WalletConnectUtils/Logger/Log.swift b/Sources/WalletConnectUtils/Logger/Log.swift index 022dcb1d8..5e3c7f785 100644 --- a/Sources/WalletConnectUtils/Logger/Log.swift +++ b/Sources/WalletConnectUtils/Logger/Log.swift @@ -1,8 +1,26 @@ import Foundation +public struct LogMessage { + public let message: String + public let properties: [String: String]? + public var aggregated: String { + var aggregatedProperties = "" + + properties?.forEach { key, value in + aggregatedProperties += "\(key): \(value), " + } + + if !aggregatedProperties.isEmpty { + aggregatedProperties = String(aggregatedProperties.dropLast(2)) + } + + return "\(message), properties: [\(aggregatedProperties)]" + } +} + public enum Log { - case error(String) - case warn(String) - case info(String) - case debug(String) + case error(LogMessage) + case warn(LogMessage) + case info(LogMessage) + case debug(LogMessage) } diff --git a/Sources/Web3Inbox/NotifyClientProxy/NotifyClientRequestSubscriber.swift b/Sources/Web3Inbox/NotifyClientProxy/NotifyClientRequestSubscriber.swift index 73b85c486..8b255bd81 100644 --- a/Sources/Web3Inbox/NotifyClientProxy/NotifyClientRequestSubscriber.swift +++ b/Sources/Web3Inbox/NotifyClientProxy/NotifyClientRequestSubscriber.swift @@ -30,14 +30,8 @@ final class NotifyClientRequestSubscriber { handle(event: .notifyDelete, params: topic) }.store(in: &publishers) - client.updateSubscriptionPublisher.sink { [unowned self] record in - switch record { - case .success(let subscription): - handle(event: .notifyUpdate, params: subscription) - case .failure: - //TODO - handle error - break - } + client.updateSubscriptionPublisher.sink { [unowned self] subscription in + handle(event: .notifyUpdate, params: subscription) }.store(in: &publishers) } } diff --git a/Sources/Web3Inbox/Web3InboxClient.swift b/Sources/Web3Inbox/Web3InboxClient.swift index 0325b6991..d6277cb4e 100644 --- a/Sources/Web3Inbox/Web3InboxClient.swift +++ b/Sources/Web3Inbox/Web3InboxClient.swift @@ -1,5 +1,6 @@ import Foundation import WebKit +import Combine public final class Web3InboxClient { @@ -19,6 +20,12 @@ public final class Web3InboxClient { private let webviewSubscriber: WebViewRequestSubscriber + public var logsPublisher: AnyPublisher { + logger.logsPublisher + .merge(with: notifyClient.logsPublisher) + .eraseToAnyPublisher() + } + init( webView: WKWebView, account: Account, @@ -46,6 +53,12 @@ public final class Web3InboxClient { setupSubscriptions() } + + public func setLogging(level: LoggingLevel) { + logger.setLogging(level: level) + notifyClient.setLogging(level: .debug) + } + public func getWebView() -> WKWebView { return webView } diff --git a/Sources/Web3Inbox/Web3InboxClientFactory.swift b/Sources/Web3Inbox/Web3InboxClientFactory.swift index 3257b4b7b..83ce998fe 100644 --- a/Sources/Web3Inbox/Web3InboxClientFactory.swift +++ b/Sources/Web3Inbox/Web3InboxClientFactory.swift @@ -12,7 +12,7 @@ final class Web3InboxClientFactory { ) -> Web3InboxClient { let url = buildUrl(account: account, config: config) - let logger = ConsoleLogger(prefix: "📬", loggingLevel: .debug) + let logger = ConsoleLogger(prefix: "📬", loggingLevel: .off) let webviewSubscriber = WebViewRequestSubscriber(url: url, logger: logger) let webView = WebViewFactory(url: url, webviewSubscriber: webviewSubscriber).create() let chatWebViewProxy = WebViewProxy(webView: webView, scriptFormatter: ChatWebViewScriptFormatter(), logger: logger) diff --git a/Sources/Web3Inbox/WebView/WebViewProxy.swift b/Sources/Web3Inbox/WebView/WebViewProxy.swift index 7aa396d93..5f4612701 100644 --- a/Sources/Web3Inbox/WebView/WebViewProxy.swift +++ b/Sources/Web3Inbox/WebView/WebViewProxy.swift @@ -19,7 +19,8 @@ actor WebViewProxy { @MainActor func respond(_ response: RPCResponse, _ request: RPCRequest) async throws { let body = try response.json(dateEncodingStrategy: .millisecondsSince1970) - logger.debug("resonding to w3i request \(request.method) with \(body)") + let logProperties: [String: String] = ["method": request.method, "requestId": "\(request.id!)", "response": body] + logger.debug("resonding to w3i request \(request.method) with \(body)", properties: logProperties) let script = scriptFormatter.formatScript(body: body) webView.evaluateJavaScript(script, completionHandler: nil) } @@ -27,7 +28,8 @@ actor WebViewProxy { @MainActor func request(_ request: RPCRequest) async throws { let body = try request.json(dateEncodingStrategy: .millisecondsSince1970) - logger.debug("requesting w3i with \(body)") + let logProperties = ["method": request.method, "requestId": "\(request.id!)"] + logger.debug("requesting w3i with \(body)", properties: logProperties) let script = scriptFormatter.formatScript(body: body) webView.evaluateJavaScript(script, completionHandler: nil) } diff --git a/Tests/WalletConnectUtilsTests/KeyedDatabaseTests.swift b/Tests/WalletConnectUtilsTests/KeyedDatabaseTests.swift index fc1674fbf..1cd08b53f 100644 --- a/Tests/WalletConnectUtilsTests/KeyedDatabaseTests.swift +++ b/Tests/WalletConnectUtilsTests/KeyedDatabaseTests.swift @@ -37,4 +37,14 @@ final class KeyedDatabaseTests: XCTestCase { XCTAssertEqual(value, updated) } + + func testOnUpdate() { + let new = Object(key: "key1", value: "value1") + + var onUpdateCalled = false + sut.onUpdate = { onUpdateCalled = true } + sut.set(element: new, for: storageKey) + + XCTAssertTrue(onUpdateCalled) + } } diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 7918fec5d..ef829c945 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -103,7 +103,7 @@ platform :ios do project: "Example/ExampleApp.xcodeproj", scheme: ENV["SCHEME"], export_method: "app-store", - xcargs: "RELAY_HOST='#{options[:relay_host]}' PROJECT_ID='#{options[:project_id]}' WALLETAPP_SENTRY_DSN='#{options[:sentry_dsn]}'" + xcargs: "RELAY_HOST='#{options[:relay_host]}' PROJECT_ID='#{options[:project_id]}' WALLETAPP_SENTRY_DSN='#{options[:sentry_dsn]}' MIXPANEL_TOKEN='#{options[:mixpanel_token]}'" ) upload_to_testflight( apple_id: ENV["APPLE_ID"], @@ -113,6 +113,7 @@ platform :ios do notify_external_testers: true, skip_waiting_for_build_processing: false, groups: [ + "WalletConnect", "WalletConnect Users" ] )