diff --git a/Example/IntegrationTests/Push/NotifyTests.swift b/Example/IntegrationTests/Push/NotifyTests.swift index 9c9fdc96d..1fbb8aaae 100644 --- a/Example/IntegrationTests/Push/NotifyTests.swift +++ b/Example/IntegrationTests/Push/NotifyTests.swift @@ -74,9 +74,11 @@ final class NotifyTests: XCTestCase { keychainStorage: keychain, environment: .sandbox) let keyserverURL = URL(string: "https://keys.walletconnect.com")! + let sqlite = try! MemorySqlite() // Note:- prod project_id do not exists on staging, we can use gmDappProjectId let client = NotifyClientFactory.create(projectId: InputConfig.gmDappProjectId, - keyserverURL: keyserverURL, + keyserverURL: keyserverURL, + sqlite: sqlite, logger: notifyLogger, keyValueStorage: keyValueStorage, keychainStorage: keychain, diff --git a/Sources/Database/DiskSqlite.swift b/Sources/Database/DiskSqlite.swift new file mode 100644 index 000000000..516d75c63 --- /dev/null +++ b/Sources/Database/DiskSqlite.swift @@ -0,0 +1,51 @@ +import Foundation +import SQLite3 + +public final class DiskSqlite: Sqlite { + + private let path: String + + private var db: OpaquePointer? + + 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) + } + var error: UnsafeMutablePointer? + guard sqlite3_exec(db, "PRAGMA journal_mode=WAL;", nil, nil, &error) == SQLITE_OK else { + let message = error.map { String(cString: $0) } + throw SQLiteError.exec(error: message) + } + } + + public func query(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) + } + sqlite3_finalize(queryStatement) + return rows + } + + public func execute(sql: String) throws { + 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() { + sqlite3_close(db) + } +} diff --git a/Sources/Database/MemorySqlite.swift b/Sources/Database/MemorySqlite.swift new file mode 100644 index 000000000..b0ea8ba3d --- /dev/null +++ b/Sources/Database/MemorySqlite.swift @@ -0,0 +1,49 @@ +import Foundation +import SQLite3 + +public final class MemorySqlite: Sqlite { + + private var db: OpaquePointer? + + public init() throws { + guard sqlite3_open_v2(":memory:", &db, SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE|SQLITE_OPEN_FULLMUTEX, nil) == SQLITE_OK else { + throw SQLiteError.openDatabaseMemory + } + var error: UnsafeMutablePointer? + guard sqlite3_exec(db, "PRAGMA journal_mode=WAL;", nil, nil, &error) == SQLITE_OK else { + let message = error.map { String(cString: $0) } + throw SQLiteError.exec(error: message) + } + } + + public func openDatabase() throws { + // No op + } + + public func query(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) + } + sqlite3_finalize(queryStatement) + return rows + } + + public func execute(sql: String) throws { + 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() { + // No op + } +} diff --git a/Sources/Database/Sqlite.swift b/Sources/Database/Sqlite.swift index 4ffa53143..1770872af 100644 --- a/Sources/Database/Sqlite.swift +++ b/Sources/Database/Sqlite.swift @@ -1,55 +1,20 @@ import Foundation import SQLite3 -public final class Sqlite { - - private var db: OpaquePointer? - - public init() { } +public protocol Sqlite { /// Opening A New Database Connection - /// - Parameter path: Path to database - public func openDatabase(path: String) 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) - } - var error: UnsafeMutablePointer? - guard sqlite3_exec(db, "PRAGMA journal_mode=WAL;", nil, nil, &error) == SQLITE_OK else { - let message = error.map { String(cString: $0) } - throw SQLiteError.exec(error: message) - } - } + func openDatabase() throws /// Evaluate an SQL Statement /// - Parameter sql: SQL query /// - Returns: Table rows array - public func query(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) - } - sqlite3_finalize(queryStatement) - return rows - } + func query(sql: String) throws -> [Row] /// One-Step query execution /// - Parameter sql: SQL query - public func execute(sql: String) throws { - 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) - } - } + func execute(sql: String) throws /// Closing A Database Connection - public func closeConnection() { - sqlite3_close(db) - } + func closeConnection() } diff --git a/Sources/Database/SqliteError.swift b/Sources/Database/SqliteError.swift index cecf7dd89..9915bca06 100644 --- a/Sources/Database/SqliteError.swift +++ b/Sources/Database/SqliteError.swift @@ -2,6 +2,7 @@ import Foundation public enum SQLiteError: Error { case openDatabase(path: String) + case openDatabaseMemory case queryPrepare(statement: String) case exec(error: String?) case decodeString(index: Int32) diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift index 3e093c089..807927a7f 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift @@ -8,11 +8,13 @@ public struct NotifyClientFactory { let keyserverURL = URL(string: "https://keys.walletconnect.com")! let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk") let groupKeychainService = GroupKeychainStorage(serviceIdentifier: groupIdentifier) + let databasePath = databasePath(appGroup: groupIdentifier, database: "notify.db") + let sqlite = DiskSqlite(path: databasePath) return NotifyClientFactory.create( projectId: projectId, keyserverURL: keyserverURL, - groupIdentifier: groupIdentifier, + sqlite: sqlite, logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychainStorage, @@ -29,7 +31,7 @@ public struct NotifyClientFactory { static func create( projectId: String, keyserverURL: URL, - groupIdentifier: String, + sqlite: Sqlite, logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, @@ -43,7 +45,7 @@ public struct NotifyClientFactory { ) -> NotifyClient { let kms = KeyManagementService(keychain: keychainStorage) let notifyAccountProvider = NotifyAccountProvider() - let database = NotifyDatabase(appGroup: groupIdentifier, database: "notify.db", sqlite: Sqlite(), logger: logger) + let database = NotifyDatabase(sqlite: sqlite, logger: logger) let notifyStorage = NotifyStorage(database: database, accountProvider: notifyAccountProvider) let identityClient = IdentityClientFactory.create(keyserver: keyserverURL, keychain: keychainStorage, logger: logger) let notifyMessageSubscriber = NotifyMessageSubscriber(keyserver: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, notifyStorage: notifyStorage, crypto: crypto, logger: logger) @@ -93,4 +95,15 @@ public struct NotifyClientFactory { subscriptionWatcher: subscriptionWatcher ) } + + static func databasePath(appGroup: String, database: String) -> String { + guard let path = FileManager.default + .containerURL(forSecurityApplicationGroupIdentifier: appGroup)? + .appendingPathComponent(database) else { + + fatalError("Database path not exists") + } + + return path.absoluteString + } } diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift index 62d1576fc..9e6110e2b 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift @@ -9,17 +9,13 @@ final class NotifyDatabase { static let messages = "NotifyMessage" } - private let appGroup: String - private let database: String private let sqlite: Sqlite private let logger: ConsoleLogging var onSubscriptionsUpdate: (() throws -> Void)? var onMessagesUpdate: (() throws -> Void)? - init(appGroup: String, database: String, sqlite: Sqlite, logger: ConsoleLogging) { - self.appGroup = appGroup - self.database = database + init(sqlite: Sqlite, logger: ConsoleLogging) { self.sqlite = sqlite self.logger = logger @@ -107,21 +103,10 @@ final class NotifyDatabase { private extension NotifyDatabase { - var path: String { - guard let path = FileManager.default - .containerURL(forSecurityApplicationGroupIdentifier: appGroup)? - .appendingPathComponent(database) else { - - fatalError("Database path not exists") - } - - return path.absoluteString - } - func prepareDatabase() { do { defer { sqlite.closeConnection() } - try sqlite.openDatabase(path: path) + try sqlite.openDatabase() try sqlite.execute(sql: """ CREATE TABLE IF NOT EXISTS \(Table.subscriptions) ( @@ -149,21 +134,21 @@ private extension NotifyDatabase { ); """) - logger.debug("SQlite database created at path \(path)") + logger.debug("SQlite database created") } catch { logger.error("SQlite database creation error: \(error.localizedDescription)") } } func execute(sql: String) throws { - try sqlite.openDatabase(path: path) + try sqlite.openDatabase() defer { sqlite.closeConnection() } try sqlite.execute(sql: sql) } func query(sql: String) throws -> [T] { - try sqlite.openDatabase(path: path) + try sqlite.openDatabase() defer { sqlite.closeConnection() } return try sqlite.query(sql: sql) diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift index 92bac470f..832e92dc3 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift @@ -3,13 +3,11 @@ import Combine protocol NotifyStoring { func getAllSubscriptions() -> [NotifySubscription] - func getSubscriptions(account: Account) throws -> [NotifySubscription] - func getSubscription(topic: String) throws -> NotifySubscription? + func getSubscriptions(account: Account) -> [NotifySubscription] + func getSubscription(topic: String) -> NotifySubscription? func setSubscription(_ subscription: NotifySubscription) throws - func replaceAllSubscriptions(_ subscriptions: [NotifySubscription]) throws func deleteSubscription(topic: String) throws func clearDatabase(account: Account) throws - func updateSubscription(_ subscription: NotifySubscription, scope: [String: ScopeValue], expiry: UInt64) throws } final class NotifyStorage: NotifyStoring { diff --git a/Tests/NotifyTests/Mocks/MockNotifyStoring.swift b/Tests/NotifyTests/Mocks/MockNotifyStoring.swift index bd773936c..e5df4531f 100644 --- a/Tests/NotifyTests/Mocks/MockNotifyStoring.swift +++ b/Tests/NotifyTests/Mocks/MockNotifyStoring.swift @@ -21,7 +21,7 @@ class MockNotifyStoring: NotifyStoring { return subscriptions } - func setSubscription(_ subscription: NotifySubscription) async throws { + func setSubscription(_ subscription: NotifySubscription) { if let index = subscriptions.firstIndex(where: { $0.topic == subscription.topic }) { subscriptions[index] = subscription } else { @@ -33,7 +33,7 @@ class MockNotifyStoring: NotifyStoring { subscriptions = subscriptions.filter { $0.account != account } } - func deleteSubscription(topic: String) async throws { + func deleteSubscription(topic: String) throws { subscriptions.removeAll(where: { $0.topic == topic }) } }