Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.9.7 #1223

Merged
merged 16 commits into from
Nov 9, 2023
Merged

1.9.7 #1223

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 72 additions & 59 deletions Example/IntegrationTests/Push/NotifyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -93,111 +93,122 @@ final class NotifyTests: XCTestCase {
}

override func setUp() {
pk = try! EthereumPrivateKey()
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)
await fulfillment(of: [expectation], timeout: InputConfig.defaultTimeout)

if let subscription {
try await walletNotifyClientA.deleteSubscription(topic: subscription.topic)
}
}

func testNotifyWatchSubscriptions() async throws {
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 {
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)

try! await walletNotifyClientA.register(account: account, domain: gmDappDomain, onSign: sign)
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)
if let subscription {
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<String>!
var didUpdate = false

walletNotifyClientA.subscriptionsPublisher
.sink { [unowned self] subscriptions in
guard
let subscription = subscriptions.first,
let scope = subscription.scope.keys.first
else { return }
let updated = expectation(description: "Subscription Updated")

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)

await fulfillment(of: [created], timeout: InputConfig.defaultTimeout)

let updateScope = Set([subscription.scope.keys.first!])
try await walletNotifyClientA.update(topic: subscription.topic, scope: updateScope)

await fulfillment(of: [updated], timeout: InputConfig.defaultTimeout)

wait(for: [expectation], 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 {
let subscribeExpectation = expectation(description: "creates notify subscription")
let messageExpectation = expectation(description: "receives a notify message")

var notifyMessage: NotifyMessage!
var notifyMessageRecord: NotifyMessageRecord?

var didNotify = false
walletNotifyClientA.subscriptionsPublisher
Expand All @@ -221,20 +232,22 @@ final class NotifyTests: XCTestCase {
}
}.store(in: &publishers)

walletNotifyClientA.notifyMessagePublisher
.sink { [unowned self] notifyMessageRecord in
XCTAssertEqual(notifyMessageRecord.message, notifyMessage)

Task(priority: .high) {
try await walletNotifyClientA.deleteSubscription(topic: notifyMessageRecord.topic)
messageExpectation.fulfill()
}
walletNotifyClientA.messagesPublisher
.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)

wait(for: [subscribeExpectation, messageExpectation], timeout: InputConfig.defaultTimeout)
await fulfillment(of: [subscribeExpectation, messageExpectation], timeout: InputConfig.defaultTimeout)

if let notifyMessageRecord {
try await walletNotifyClientA.deleteSubscription(topic: notifyMessageRecord.topic)
}
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ let package = Package(
dependencies: ["WalletConnectUtils", "WalletConnectNetworking"]),
.target(
name: "Database",
dependencies: []),
dependencies: ["WalletConnectUtils"]),
.target(
name: "WalletConnectModal",
dependencies: ["QRCode", "WalletConnectSign"],
Expand Down
3 changes: 3 additions & 0 deletions Sources/Database/DatabaseImports.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#if !CocoaPods
@_exported import WalletConnectUtils
#endif
46 changes: 28 additions & 18 deletions Sources/Database/DiskSqlite.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,50 @@ public final class DiskSqlite: Sqlite {

private var db: OpaquePointer?

private let lock = UnfairLock()

public init(path: String) {
self.path = path
}

public func openDatabase() throws {
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<Row: SqliteRow>(sql: String) throws -> [Row] {
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 {
var error: UnsafeMutablePointer<CChar>?
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<CChar>?
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() {
sqlite3_close(db)
lock.locked {
sqlite3_close(db)
}
}
}
22 changes: 21 additions & 1 deletion Sources/WalletConnectModal/Extensions/View+Backport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
12 changes: 4 additions & 8 deletions Sources/WalletConnectModal/Modal/ModalContainerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 7 additions & 3 deletions Sources/WalletConnectModal/Modal/Screens/QRCodeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -185,7 +185,7 @@ struct WalletDetail: View {
.foregroundColor(.foreground2)
}
}
.onTapGesture {
.onTapGestureBackported {
viewModel.handle(.didTapAppStore)
}
}
Expand Down
Loading
Loading