From 2ecd85d3ba22c5bfbee3b6d1e8785b025eede298 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 7 Nov 2023 23:40:11 +0800 Subject: [PATCH 01/12] NotifyClient publishers updated --- Example/IntegrationTests/Push/NotifyTests.swift | 5 +++-- .../Wallet/Notifications/NotificationsInteractor.swift | 2 +- .../Client/Wallet/NotifyClient.swift | 8 ++++++-- .../Client/Wallet/NotifyStorage.swift | 10 ++++++++++ .../wc_notifyMessage/NotifyMessageSubscriber.swift | 6 ------ 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/Example/IntegrationTests/Push/NotifyTests.swift b/Example/IntegrationTests/Push/NotifyTests.swift index 1fbb8aaae..44bb7fd4c 100644 --- a/Example/IntegrationTests/Push/NotifyTests.swift +++ b/Example/IntegrationTests/Push/NotifyTests.swift @@ -221,8 +221,9 @@ final class NotifyTests: XCTestCase { } }.store(in: &publishers) - walletNotifyClientA.notifyMessagePublisher - .sink { [unowned self] notifyMessageRecord in + walletNotifyClientA.messagesPublisher + .sink { [unowned self] messages in + guard let notifyMessageRecord = messages.first else { return } XCTAssertEqual(notifyMessageRecord.message, notifyMessage) Task(priority: .high) { diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsInteractor.swift index 1a1bd5b8f..a7f6c0c19 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsInteractor.swift @@ -5,7 +5,7 @@ import Combine final class NotificationsInteractor { var subscriptionsPublisher: AnyPublisher<[NotifySubscription], Never> { - return Notify.instance.subscriptionsPublisher + return Notify.instance.subscriptionsPublisher(account: importAccount.account) } private let importAccount: ImportAccount diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift index bba5af964..41db7b97e 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift @@ -9,8 +9,8 @@ public class NotifyClient { return notifyStorage.subscriptionsPublisher } - public var notifyMessagePublisher: AnyPublisher { - return notifyMessageSubscriber.notifyMessagePublisher + public var messagesPublisher: AnyPublisher<[NotifyMessageRecord], Never> { + return notifyStorage.messagesPublisher } public var logsPublisher: AnyPublisher { @@ -125,6 +125,10 @@ public class NotifyClient { return identityService.isIdentityRegistered(account: account) } + public func subscriptionsPublisher(account: Account) -> AnyPublisher<[NotifySubscription], Never> { + return notifyStorage.subscriptionsPublisher(account: account) + } + public func messagesPublisher(topic: String) -> AnyPublisher<[NotifyMessageRecord], Never> { return notifyStorage.messagesPublisher(topic: topic) } diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift index 832e92dc3..1e9cf905f 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift @@ -40,6 +40,10 @@ final class NotifyStorage: NotifyStoring { return subscriptionsSubject.eraseToAnyPublisher() } + var messagesPublisher: AnyPublisher<[NotifyMessageRecord], Never> { + return messagesSubject.eraseToAnyPublisher() + } + init(database: NotifyDatabase, accountProvider: NotifyAccountProvider) { self.database = database self.accountProvider = accountProvider @@ -89,6 +93,12 @@ final class NotifyStorage: NotifyStoring { updateSubscriptionSubject.send(updated) } + func subscriptionsPublisher(account: Account) -> AnyPublisher<[NotifySubscription], Never> { + return subscriptionsSubject + .map { $0.filter { $0.account == account } } + .eraseToAnyPublisher() + } + // MARK: Messages func messagesPublisher(topic: String) -> AnyPublisher<[NotifyMessageRecord], Never> { diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift index 9b9a2cd8f..4199bfb6f 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift @@ -8,11 +8,6 @@ class NotifyMessageSubscriber { private let notifyStorage: NotifyStorage private let crypto: CryptoProvider private let logger: ConsoleLogging - private let notifyMessagePublisherSubject = PassthroughSubject() - - public var notifyMessagePublisher: AnyPublisher { - notifyMessagePublisherSubject.eraseToAnyPublisher() - } init(keyserver: URL, networkingInteractor: NetworkInteracting, identityClient: IdentityClient, notifyStorage: NotifyStorage, crypto: CryptoProvider, logger: ConsoleLogging) { self.keyserver = keyserver @@ -39,7 +34,6 @@ class NotifyMessageSubscriber { let dappPubKey = try DIDKey(did: claims.iss) let record = NotifyMessageRecord(id: payload.id.string, topic: payload.topic, message: messagePayload.message, publishedAt: payload.publishedAt) try notifyStorage.setMessage(record) - notifyMessagePublisherSubject.send(record) let receiptPayload = NotifyMessageReceiptPayload( account: messagePayload.account, From 31887b39e5f749f82196f3a3ad6c563b26e7f2aa Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Mon, 6 Nov 2023 11:01:05 +0800 Subject: [PATCH 02/12] Sqlite lock --- Sources/Database/DiskSqlite.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/Database/DiskSqlite.swift b/Sources/Database/DiskSqlite.swift index 19ffba05e..bc241086b 100644 --- a/Sources/Database/DiskSqlite.swift +++ b/Sources/Database/DiskSqlite.swift @@ -7,11 +7,14 @@ public final class DiskSqlite: Sqlite { private var db: OpaquePointer? + private let lock = NSLock() + public init(path: String) { self.path = path } public func openDatabase() throws { + defer { lock.lock() } guard sqlite3_open_v2(path, &db, SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE|SQLITE_OPEN_FULLMUTEX, nil) == SQLITE_OK else { throw SQLiteError.openDatabase(path: path) } @@ -41,6 +44,7 @@ public final class DiskSqlite: Sqlite { } public func closeConnection() { + defer { lock.unlock() } sqlite3_close(db) } } From 92a6de8875320d3ffd889b8a2fa83b0d74cd5698 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 7 Nov 2023 01:36:11 +0800 Subject: [PATCH 03/12] lock on every db access --- Sources/Database/DiskSqlite.swift | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Sources/Database/DiskSqlite.swift b/Sources/Database/DiskSqlite.swift index bc241086b..1c13cb827 100644 --- a/Sources/Database/DiskSqlite.swift +++ b/Sources/Database/DiskSqlite.swift @@ -14,13 +14,18 @@ public final class DiskSqlite: Sqlite { } public func openDatabase() throws { - defer { lock.lock() } + defer { lock.unlock() } + lock.lock() + guard sqlite3_open_v2(path, &db, SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE|SQLITE_OPEN_FULLMUTEX, nil) == SQLITE_OK else { throw SQLiteError.openDatabase(path: path) } } public func query(sql: String) throws -> [Row] { + defer { lock.unlock() } + lock.lock() + var queryStatement: OpaquePointer? guard sqlite3_prepare_v2(db, sql, -1, &queryStatement, nil) == SQLITE_OK else { throw SQLiteError.queryPrepare(statement: sql) @@ -36,6 +41,9 @@ public final class DiskSqlite: Sqlite { } public func execute(sql: String) throws { + defer { lock.unlock() } + lock.lock() + var error: UnsafeMutablePointer? guard sqlite3_exec(db, sql, nil, nil, &error) == SQLITE_OK else { let message = error.map { String(cString: $0) } @@ -45,6 +53,8 @@ public final class DiskSqlite: Sqlite { public func closeConnection() { defer { lock.unlock() } + lock.lock() + sqlite3_close(db) } } From aa9025fe518b1ba790c1efef39cff90691f958c2 Mon Sep 17 00:00:00 2001 From: Radek Novak Date: Wed, 8 Nov 2023 12:37:32 +0100 Subject: [PATCH 04/12] Backport apis & try targeting just iOS --- .../Extensions/View+Backport.swift | 22 ++++++++++++- .../Modal/ModalContainerView.swift | 12 +++---- .../Modal/Screens/QRCodeView.swift | 10 ++++-- .../Screens/WalletDetail/WalletDetail.swift | 4 +-- .../Modal/Screens/WalletList.swift | 32 +++++++------------ .../UI/Common/Web3ModalPicker.swift | 2 +- WalletConnectSwiftV2.podspec | 1 + 7 files changed, 48 insertions(+), 35 deletions(-) diff --git a/Sources/WalletConnectModal/Extensions/View+Backport.swift b/Sources/WalletConnectModal/Extensions/View+Backport.swift index b93de0e16..3c40e44ba 100644 --- a/Sources/WalletConnectModal/Extensions/View+Backport.swift +++ b/Sources/WalletConnectModal/Extensions/View+Backport.swift @@ -3,7 +3,7 @@ import SwiftUI extension View { - #if os(iOS) + #if os(iOS) || os(tvOS) /// A backwards compatible wrapper for iOS 14 `onChange` @ViewBuilder @@ -27,4 +27,24 @@ extension View { } #endif + + #if os(iOS) || os(macOS) + + @ViewBuilder + func onTapGestureBackported(count: Int = 1, perform action: @escaping () -> Void) -> some View { + self + } + + #elseif os(tvOS) + + @ViewBuilder + func onTapGestureBackported(count: Int = 1, perform action: @escaping () -> Void) -> some View { + if #available(tvOS 16.0, *) { + self.onTapGesture(count: count, perform: action) + } else { + self + } + } + + #endif } diff --git a/Sources/WalletConnectModal/Modal/ModalContainerView.swift b/Sources/WalletConnectModal/Modal/ModalContainerView.swift index ab24e444b..ba5ffb9cf 100644 --- a/Sources/WalletConnectModal/Modal/ModalContainerView.swift +++ b/Sources/WalletConnectModal/Modal/ModalContainerView.swift @@ -25,14 +25,10 @@ struct ModalContainerView: View { Color.thickOverlay .colorScheme(.light) .opacity(showModal ? 1 : 0) - .transform { - #if os(iOS) - $0.onTapGesture { - withAnimation { - showModal = false - } - } - #endif + .onTapGestureBackported { + withAnimation { + showModal = false + } } ) .edgesIgnoringSafeArea(.all) diff --git a/Sources/WalletConnectModal/Modal/Screens/QRCodeView.swift b/Sources/WalletConnectModal/Modal/Screens/QRCodeView.swift index ab78e24cb..e889fd5c7 100644 --- a/Sources/WalletConnectModal/Modal/Screens/QRCodeView.swift +++ b/Sources/WalletConnectModal/Modal/Screens/QRCodeView.swift @@ -59,9 +59,13 @@ struct QRCodeView: View { ) ) - return doc.imageUI( - size, label: Text("QR code with URI") - )! + if #available(macOS 11, *) { + return doc.imageUI( + size, label: Text("QR code with URI") + )! + } else { + return Image.init(sfSymbolName: "qrcode") + } } } diff --git a/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetail.swift b/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetail.swift index 2fb5f1bff..6ff5a740a 100644 --- a/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetail.swift +++ b/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetail.swift @@ -31,7 +31,7 @@ struct WalletDetail: View { .contentShape(Rectangle()) .padding(.horizontal, 8) .padding(.vertical, 8) - .onTapGesture { + .onTapGestureBackported { withAnimation(.easeInOut(duration: 0.15)) { viewModel.preferredPlatform = item } @@ -185,7 +185,7 @@ struct WalletDetail: View { .foregroundColor(.foreground2) } } - .onTapGesture { + .onTapGestureBackported { viewModel.handle(.didTapAppStore) } } diff --git a/Sources/WalletConnectModal/Modal/Screens/WalletList.swift b/Sources/WalletConnectModal/Modal/Screens/WalletList.swift index 7ea02d286..55ba54c2a 100644 --- a/Sources/WalletConnectModal/Modal/Screens/WalletList.swift +++ b/Sources/WalletConnectModal/Modal/Screens/WalletList.swift @@ -58,14 +58,10 @@ struct WalletList: View { if wallets.count > numberOfColumns * 2 { viewAllItem() - .transform { - #if os(iOS) - $0.onTapGesture { - withAnimation { - navigateTo(.viewAll) - } - } - #endif + .onTapGestureBackported { + withAnimation { + navigateTo(.viewAll) + } } } } @@ -181,19 +177,15 @@ struct WalletList: View { .padding(.horizontal, 12) } .frame(maxWidth: 80, maxHeight: 96) - .transform { - #if os(iOS) - $0.onTapGesture { - withAnimation { - navigateTo(.walletDetail(wallet)) - - // Small delay to let detail screen present before actually deeplinking - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - onListingTap(wallet) - } - } + .onTapGestureBackported { + withAnimation { + navigateTo(.walletDetail(wallet)) + + // Small delay to let detail screen present before actually deeplinking + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + onListingTap(wallet) } - #endif + } } } } diff --git a/Sources/WalletConnectModal/UI/Common/Web3ModalPicker.swift b/Sources/WalletConnectModal/UI/Common/Web3ModalPicker.swift index 31ba098ad..49ce87219 100644 --- a/Sources/WalletConnectModal/UI/Common/Web3ModalPicker.swift +++ b/Sources/WalletConnectModal/UI/Common/Web3ModalPicker.swift @@ -104,7 +104,7 @@ struct PreviewWeb3ModalPicker: View { .contentShape(Rectangle()) .padding(.horizontal, 8) .padding(.vertical, 8) - .onTapGesture { + .onTapGestureBackported { withAnimation(.easeInOut(duration: 0.15)) { selectedItem = item } diff --git a/WalletConnectSwiftV2.podspec b/WalletConnectSwiftV2.podspec index 29cf98ffe..48a97bf66 100644 --- a/WalletConnectSwiftV2.podspec +++ b/WalletConnectSwiftV2.podspec @@ -194,5 +194,6 @@ Pod::Spec.new do |spec| ss.source_files = 'Sources/WalletConnectModal/**/*.{h,m,swift}' ss.dependency 'WalletConnectSwiftV2/WalletConnectSign' ss.dependency 'DSF_QRCode', '~> 16.1.1' + ss.platform = :ios end end From ab631e959baa00f218be2e7a1d952f287ae1fecc Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Mon, 6 Nov 2023 19:00:39 +0800 Subject: [PATCH 05/12] testWalletCreatesAndUpdatesSubscription refactor --- .../IntegrationTests/Push/NotifyTests.swift | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/Example/IntegrationTests/Push/NotifyTests.swift b/Example/IntegrationTests/Push/NotifyTests.swift index 44bb7fd4c..fbad32a7c 100644 --- a/Example/IntegrationTests/Push/NotifyTests.swift +++ b/Example/IntegrationTests/Push/NotifyTests.swift @@ -18,7 +18,7 @@ final class NotifyTests: XCTestCase { let gmDappDomain = InputConfig.gmDappHost - let pk = try! EthereumPrivateKey() + var pk: EthereumPrivateKey! var privateKey: Data { return Data(pk.rawPrivateKey) @@ -93,6 +93,7 @@ final class NotifyTests: XCTestCase { } override func setUp() { + pk = try! EthereumPrivateKey() walletNotifyClientA = makeWalletClient() } @@ -156,41 +157,42 @@ final class NotifyTests: XCTestCase { try await clientB.deleteSubscription(topic: subscription.topic) } - func testWalletCreatesAndUpdatesSubscription() async { - let expectation = expectation(description: "expects to create and update notify subscription") - expectation.assertForOverFulfill = false + func testWalletCreatesAndUpdatesSubscription() async throws { + let created = expectation(description: "Subscription created") - var updateScope: Set! - var didUpdate = false + let updated = expectation(description: "Subscription Updated") - walletNotifyClientA.subscriptionsPublisher - .sink { [unowned self] subscriptions in - guard - let subscription = subscriptions.first, - let scope = subscription.scope.keys.first - else { return } - - let updatedScope = Set(subscription.scope.filter { $0.value.enabled == true }.keys) + var isCreated = false + var isUpdated = false + var subscription: NotifySubscription! - if !didUpdate { - updateScope = Set([scope]) - didUpdate = true - Task(priority: .high) { - try await walletNotifyClientA.update(topic: subscription.topic, scope: Set([scope])) - } - } - if updateScope == updatedScope { - Task(priority: .high) { - try await walletNotifyClientA.deleteSubscription(topic: subscription.topic) - expectation.fulfill() - } + walletNotifyClientA.subscriptionsPublisher + .sink { subscriptions in + subscription = subscriptions.first + + if !isCreated { + isCreated = true + created.fulfill() + } else if !isUpdated { + isUpdated = true + updated.fulfill() } }.store(in: &publishers) - try! await walletNotifyClientA.register(account: account, domain: gmDappDomain, onSign: sign) - try! await walletNotifyClientA.subscribe(appDomain: gmDappDomain, account: account) + try await walletNotifyClientA.register(account: account, domain: gmDappDomain, onSign: sign) + try await walletNotifyClientA.subscribe(appDomain: gmDappDomain, account: account) - wait(for: [expectation], timeout: InputConfig.defaultTimeout) + wait(for: [created], timeout: InputConfig.defaultTimeout) + + let updateScope = Set([subscription.scope.keys.first!]) + try await walletNotifyClientA.update(topic: subscription.topic, scope: updateScope) + + wait(for: [updated], timeout: InputConfig.defaultTimeout) + + let updatedScope = Set(subscription.scope.filter { $0.value.enabled == true }.keys) + XCTAssertEqual(updatedScope, updateScope) + + try await walletNotifyClientA.deleteSubscription(topic: subscription.topic) } func testNotifyServerSubscribeAndNotifies() async throws { From 44399aea7d73e53d9cd9fc1dcce4b41c44123834 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 7 Nov 2023 15:44:43 +0800 Subject: [PATCH 06/12] testWalletCreatesSubscription refactored --- .../IntegrationTests/Push/NotifyTests.swift | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Example/IntegrationTests/Push/NotifyTests.swift b/Example/IntegrationTests/Push/NotifyTests.swift index fbad32a7c..b0d7f7608 100644 --- a/Example/IntegrationTests/Push/NotifyTests.swift +++ b/Example/IntegrationTests/Push/NotifyTests.swift @@ -97,22 +97,26 @@ final class NotifyTests: XCTestCase { walletNotifyClientA = makeWalletClient() } - func testWalletCreatesSubscription() async { + func testWalletCreatesSubscription() async throws { let expectation = expectation(description: "expects to create notify subscription") + expectation.assertForOverFulfill = false + + var subscription: NotifySubscription? walletNotifyClientA.subscriptionsPublisher - .sink { [unowned self] subscriptions in - guard let subscription = subscriptions.first else { return } - Task(priority: .high) { - try await walletNotifyClientA.deleteSubscription(topic: subscription.topic) - expectation.fulfill() - } + .sink { subscriptions in + subscription = subscriptions.first + expectation.fulfill() }.store(in: &publishers) - try! await walletNotifyClientA.register(account: account, domain: gmDappDomain, onSign: sign) - try! await walletNotifyClientA.subscribe(appDomain: gmDappDomain, account: account) + try await walletNotifyClientA.register(account: account, domain: gmDappDomain, onSign: sign) + try await walletNotifyClientA.subscribe(appDomain: gmDappDomain, account: account) wait(for: [expectation], timeout: InputConfig.defaultTimeout) + + if let subscription { + try await walletNotifyClientA.deleteSubscription(topic: subscription.topic) + } } func testNotifyWatchSubscriptions() async throws { From 33af3b0deff22ca85a232284cdb2d005900bad32 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 7 Nov 2023 15:47:27 +0800 Subject: [PATCH 07/12] testNotifyWatchSubscriptions refactored --- .../IntegrationTests/Push/NotifyTests.swift | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/Example/IntegrationTests/Push/NotifyTests.swift b/Example/IntegrationTests/Push/NotifyTests.swift index b0d7f7608..1bc5bcc7e 100644 --- a/Example/IntegrationTests/Push/NotifyTests.swift +++ b/Example/IntegrationTests/Push/NotifyTests.swift @@ -112,7 +112,7 @@ final class NotifyTests: XCTestCase { try await walletNotifyClientA.register(account: account, domain: gmDappDomain, onSign: sign) try await walletNotifyClientA.subscribe(appDomain: gmDappDomain, account: account) - wait(for: [expectation], timeout: InputConfig.defaultTimeout) + await fulfillment(of: [expectation], timeout: InputConfig.defaultTimeout) if let subscription { try await walletNotifyClientA.deleteSubscription(topic: subscription.topic) @@ -123,20 +123,23 @@ final class NotifyTests: XCTestCase { let expectation = expectation(description: "expects client B to receive subscription created by client A") expectation.assertForOverFulfill = false + var subscription: NotifySubscription? + let clientB = makeWalletClient(prefix: "👐🏼 Wallet B: ") clientB.subscriptionsPublisher.sink { subscriptions in - guard let subscription = subscriptions.first else { return } - Task(priority: .high) { - try await clientB.deleteSubscription(topic: subscription.topic) - expectation.fulfill() - } + subscription = subscriptions.first + expectation.fulfill() }.store(in: &publishers) try! await walletNotifyClientA.register(account: account, domain: gmDappDomain, onSign: sign) try! await walletNotifyClientA.subscribe(appDomain: gmDappDomain, account: account) try! await clientB.register(account: account, domain: gmDappDomain, onSign: sign) - wait(for: [expectation], timeout: InputConfig.defaultTimeout) + await fulfillment(of: [expectation], timeout: InputConfig.defaultTimeout) + + if let subscription { + try await clientB.deleteSubscription(topic: subscription.topic) + } } func testNotifySubscriptionChanged() async throws { @@ -156,7 +159,7 @@ final class NotifyTests: XCTestCase { try! await clientB.register(account: account, domain: gmDappDomain, onSign: sign) try! await walletNotifyClientA.subscribe(appDomain: gmDappDomain, account: account) - wait(for: [expectation], timeout: InputConfig.defaultTimeout) + await fulfillment(of: [expectation], timeout: InputConfig.defaultTimeout) try await clientB.deleteSubscription(topic: subscription.topic) } @@ -186,12 +189,12 @@ final class NotifyTests: XCTestCase { try await walletNotifyClientA.register(account: account, domain: gmDappDomain, onSign: sign) try await walletNotifyClientA.subscribe(appDomain: gmDappDomain, account: account) - wait(for: [created], timeout: InputConfig.defaultTimeout) + await fulfillment(of: [created], timeout: InputConfig.defaultTimeout) let updateScope = Set([subscription.scope.keys.first!]) try await walletNotifyClientA.update(topic: subscription.topic, scope: updateScope) - wait(for: [updated], timeout: InputConfig.defaultTimeout) + await fulfillment(of: [updated], timeout: InputConfig.defaultTimeout) let updatedScope = Set(subscription.scope.filter { $0.value.enabled == true }.keys) XCTAssertEqual(updatedScope, updateScope) @@ -241,7 +244,7 @@ final class NotifyTests: XCTestCase { try! await walletNotifyClientA.register(account: account, domain: gmDappDomain, onSign: sign) try! await walletNotifyClientA.subscribe(appDomain: gmDappDomain, account: account) - wait(for: [subscribeExpectation, messageExpectation], timeout: InputConfig.defaultTimeout) + await fulfillment(of: [subscribeExpectation, messageExpectation], timeout: InputConfig.defaultTimeout) } } From 9ed4757f4094e9d16fb0989c8b4338004ac84766 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 7 Nov 2023 15:50:43 +0800 Subject: [PATCH 08/12] testNotifyServerSubscribeAndNotifies refactored --- .../IntegrationTests/Push/NotifyTests.swift | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/Example/IntegrationTests/Push/NotifyTests.swift b/Example/IntegrationTests/Push/NotifyTests.swift index 1bc5bcc7e..18408e9af 100644 --- a/Example/IntegrationTests/Push/NotifyTests.swift +++ b/Example/IntegrationTests/Push/NotifyTests.swift @@ -146,12 +146,11 @@ final class NotifyTests: XCTestCase { let expectation = expectation(description: "expects client B to receive subscription after both clients are registered and client A creates one") expectation.assertForOverFulfill = false - var subscription: NotifySubscription! + var subscription: NotifySubscription? let clientB = makeWalletClient(prefix: "👐🏼 Wallet B: ") clientB.subscriptionsPublisher.sink { subscriptions in - guard let newSubscription = subscriptions.first else { return } - subscription = newSubscription + subscription = subscriptions.first expectation.fulfill() }.store(in: &publishers) @@ -161,7 +160,9 @@ final class NotifyTests: XCTestCase { await fulfillment(of: [expectation], timeout: InputConfig.defaultTimeout) - try await clientB.deleteSubscription(topic: subscription.topic) + if let subscription { + try await clientB.deleteSubscription(topic: subscription.topic) + } } func testWalletCreatesAndUpdatesSubscription() async throws { @@ -207,6 +208,7 @@ final class NotifyTests: XCTestCase { let messageExpectation = expectation(description: "receives a notify message") var notifyMessage: NotifyMessage! + var notifyMessageRecord: NotifyMessageRecord? var didNotify = false walletNotifyClientA.subscriptionsPublisher @@ -231,20 +233,21 @@ final class NotifyTests: XCTestCase { }.store(in: &publishers) walletNotifyClientA.messagesPublisher - .sink { [unowned self] messages in - guard let notifyMessageRecord = messages.first else { return } - XCTAssertEqual(notifyMessageRecord.message, notifyMessage) - - Task(priority: .high) { - try await walletNotifyClientA.deleteSubscription(topic: notifyMessageRecord.topic) - messageExpectation.fulfill() - } + .sink { messages in + guard let newNotifyMessageRecord = messages.first else { return } + XCTAssertEqual(newNotifyMessageRecord.message, notifyMessage) + notifyMessageRecord = newNotifyMessageRecord + messageExpectation.fulfill() }.store(in: &publishers) try! await walletNotifyClientA.register(account: account, domain: gmDappDomain, onSign: sign) try! await walletNotifyClientA.subscribe(appDomain: gmDappDomain, account: account) await fulfillment(of: [subscribeExpectation, messageExpectation], timeout: InputConfig.defaultTimeout) + + if let notifyMessageRecord { + try await walletNotifyClientA.deleteSubscription(topic: notifyMessageRecord.topic) + } } } From 5fb1043ec7e05c196a6a7f5caa9c8491e3ee4e66 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Wed, 8 Nov 2023 20:25:49 +0800 Subject: [PATCH 09/12] Unfair lock --- Package.swift | 8 +-- Sources/Database/DatabaseImports.swift | 3 ++ Sources/Database/DiskSqlite.swift | 58 ++++++++++----------- Sources/WalletConnectUtils/UnfairLock.swift | 21 ++++++++ 4 files changed, 52 insertions(+), 38 deletions(-) create mode 100644 Sources/Database/DatabaseImports.swift create mode 100644 Sources/WalletConnectUtils/UnfairLock.swift diff --git a/Package.swift b/Package.swift index 0f7ce4e77..93c74e302 100644 --- a/Package.swift +++ b/Package.swift @@ -37,15 +37,9 @@ let package = Package( .library( name: "WalletConnectNetworking", targets: ["WalletConnectNetworking"]), - .library( - name: "WalletConnectSync", - targets: ["WalletConnectSync"]), .library( name: "WalletConnectVerify", targets: ["WalletConnectVerify"]), - .library( - name: "WalletConnectHistory", - targets: ["WalletConnectHistory"]), .library( name: "WalletConnectModal", targets: ["WalletConnectModal"]), @@ -132,7 +126,7 @@ let package = Package( dependencies: ["WalletConnectUtils", "WalletConnectNetworking"]), .target( name: "Database", - dependencies: []), + dependencies: ["WalletConnectUtils"]), .target( name: "WalletConnectModal", dependencies: ["QRCode", "WalletConnectSign"], diff --git a/Sources/Database/DatabaseImports.swift b/Sources/Database/DatabaseImports.swift new file mode 100644 index 000000000..39a0c89b7 --- /dev/null +++ b/Sources/Database/DatabaseImports.swift @@ -0,0 +1,3 @@ +#if !CocoaPods +@_exported import WalletConnectUtils +#endif diff --git a/Sources/Database/DiskSqlite.swift b/Sources/Database/DiskSqlite.swift index 1c13cb827..8d7b77c49 100644 --- a/Sources/Database/DiskSqlite.swift +++ b/Sources/Database/DiskSqlite.swift @@ -7,54 +7,50 @@ public final class DiskSqlite: Sqlite { private var db: OpaquePointer? - private let lock = NSLock() + private let lock = UnfairLock() public init(path: String) { self.path = path } public func openDatabase() throws { - defer { lock.unlock() } - lock.lock() - - guard sqlite3_open_v2(path, &db, SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE|SQLITE_OPEN_FULLMUTEX, nil) == SQLITE_OK else { - throw SQLiteError.openDatabase(path: path) + try lock.locked { + guard sqlite3_open_v2(path, &db, SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE|SQLITE_OPEN_FULLMUTEX, nil) == SQLITE_OK else { + throw SQLiteError.openDatabase(path: path) + } } } public func query(sql: String) throws -> [Row] { - defer { lock.unlock() } - lock.lock() - - var queryStatement: OpaquePointer? - guard sqlite3_prepare_v2(db, sql, -1, &queryStatement, nil) == SQLITE_OK else { - throw SQLiteError.queryPrepare(statement: sql) - } - var rows: [Row] = [] - while sqlite3_step(queryStatement) == SQLITE_ROW { - let decoder = SqliteRowDecoder(statement: queryStatement) - guard let row = try? Row(decoder: decoder) else { continue } - rows.append(row) + return try lock.locked { + var queryStatement: OpaquePointer? + guard sqlite3_prepare_v2(db, sql, -1, &queryStatement, nil) == SQLITE_OK else { + throw SQLiteError.queryPrepare(statement: sql) + } + var rows: [Row] = [] + while sqlite3_step(queryStatement) == SQLITE_ROW { + let decoder = SqliteRowDecoder(statement: queryStatement) + guard let row = try? Row(decoder: decoder) else { continue } + rows.append(row) + } + sqlite3_finalize(queryStatement) + return rows } - sqlite3_finalize(queryStatement) - return rows } public func execute(sql: String) throws { - defer { lock.unlock() } - lock.lock() - - var error: UnsafeMutablePointer? - guard sqlite3_exec(db, sql, nil, nil, &error) == SQLITE_OK else { - let message = error.map { String(cString: $0) } - throw SQLiteError.exec(error: message) + try lock.locked { + var error: UnsafeMutablePointer? + guard sqlite3_exec(db, sql, nil, nil, &error) == SQLITE_OK else { + let message = error.map { String(cString: $0) } + throw SQLiteError.exec(error: message) + } } } public func closeConnection() { - defer { lock.unlock() } - lock.lock() - - sqlite3_close(db) + lock.locked { + sqlite3_close(db) + } } } diff --git a/Sources/WalletConnectUtils/UnfairLock.swift b/Sources/WalletConnectUtils/UnfairLock.swift new file mode 100644 index 000000000..3e22d97d9 --- /dev/null +++ b/Sources/WalletConnectUtils/UnfairLock.swift @@ -0,0 +1,21 @@ +import Foundation + +public final class UnfairLock { + private var lock: UnsafeMutablePointer + + public init() { + lock = UnsafeMutablePointer.allocate(capacity: 1) + lock.initialize(to: os_unfair_lock()) + } + + deinit { + lock.deallocate() + } + + @discardableResult + public func locked(_ f: () throws -> ReturnValue) rethrows -> ReturnValue { + os_unfair_lock_lock(lock) + defer { os_unfair_lock_unlock(lock) } + return try f() + } +} From 443adae3468cc2ba625e56a32af972d2b0a0d63e Mon Sep 17 00:00:00 2001 From: Radek Novak Date: Wed, 8 Nov 2023 13:28:34 +0100 Subject: [PATCH 10/12] Re-enable tvOS --- WalletConnectSwiftV2.podspec | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/WalletConnectSwiftV2.podspec b/WalletConnectSwiftV2.podspec index 48a97bf66..9cf68e052 100644 --- a/WalletConnectSwiftV2.podspec +++ b/WalletConnectSwiftV2.podspec @@ -194,6 +194,9 @@ Pod::Spec.new do |spec| ss.source_files = 'Sources/WalletConnectModal/**/*.{h,m,swift}' ss.dependency 'WalletConnectSwiftV2/WalletConnectSign' ss.dependency 'DSF_QRCode', '~> 16.1.1' - ss.platform = :ios + ss.ios.deployment_target = ios_deployment_target + ss.tvos.deployment_target = tvos_deployment_target + # TODO: Re-add macOS support once + ss.osx.deployment_target = '' end end From cfe81d670a6d44445ad87752cf400abeecbca2fb Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Wed, 8 Nov 2023 20:28:36 +0800 Subject: [PATCH 11/12] Package.swift reverted --- Package.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Package.swift b/Package.swift index 93c74e302..dcfa42b87 100644 --- a/Package.swift +++ b/Package.swift @@ -37,9 +37,15 @@ let package = Package( .library( name: "WalletConnectNetworking", targets: ["WalletConnectNetworking"]), + .library( + name: "WalletConnectSync", + targets: ["WalletConnectSync"]), .library( name: "WalletConnectVerify", targets: ["WalletConnectVerify"]), + .library( + name: "WalletConnectHistory", + targets: ["WalletConnectHistory"]), .library( name: "WalletConnectModal", targets: ["WalletConnectModal"]), From 3aa5c58065cbd7db366ee230134eb849b1c6c5f7 Mon Sep 17 00:00:00 2001 From: flypaper0 Date: Wed, 8 Nov 2023 16:12:11 +0100 Subject: [PATCH 12/12] Set User Agent --- Sources/WalletConnectRelay/PackageConfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json index f828972fc..68198226b 100644 --- a/Sources/WalletConnectRelay/PackageConfig.json +++ b/Sources/WalletConnectRelay/PackageConfig.json @@ -1 +1 @@ -{"version": "1.9.6"} +{"version": "1.9.7"}