From a717e4e12d47d29f56f9a9b13de309eee02483cd Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Wed, 25 Oct 2023 19:06:26 +0800 Subject: [PATCH 1/8] Database package --- .../xcshareddata/xcschemes/Database.xcscheme | 66 +++++++++++++++++++ Package.swift | 6 ++ Sources/Database/Sqlite.swift | 53 +++++++++++++++ Sources/Database/SqliteError.swift | 8 +++ Sources/Database/SqliteRow.swift | 7 ++ Sources/Database/SqliteRowDecoder.swift | 29 ++++++++ 6 files changed, 169 insertions(+) create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Database.xcscheme create mode 100644 Sources/Database/Sqlite.swift create mode 100644 Sources/Database/SqliteError.swift create mode 100644 Sources/Database/SqliteRow.swift create mode 100644 Sources/Database/SqliteRowDecoder.swift diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Database.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Database.xcscheme new file mode 100644 index 000000000..af7725d2f --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/Database.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Package.swift b/Package.swift index b4c69b94b..052bd1772 100644 --- a/Package.swift +++ b/Package.swift @@ -49,6 +49,9 @@ let package = Package( .library( name: "WalletConnectModal", targets: ["WalletConnectModal"]), + .library( + name: "Database", // TODO: Remove library + targets: ["Database"]), ], dependencies: [ @@ -130,6 +133,9 @@ let package = Package( .target( name: "WalletConnectVerify", dependencies: ["WalletConnectUtils", "WalletConnectNetworking"]), + .target( + name: "Database", + dependencies: []), .target( name: "WalletConnectModal", dependencies: ["QRCode", "WalletConnectSign"], diff --git a/Sources/Database/Sqlite.swift b/Sources/Database/Sqlite.swift new file mode 100644 index 000000000..e6f008cde --- /dev/null +++ b/Sources/Database/Sqlite.swift @@ -0,0 +1,53 @@ +import Foundation +import SQLite3 + +public final class Sqlite { + + private var db: OpaquePointer? + + /// Opening A New Database Connection + /// - Parameter path: Path to database + public func openDatabase(path: String) throws { + guard sqlite3_open(path, &db) == 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) + } + } + + /// 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 + } + + /// One-Step query execution + /// - Parameter sql: SQL query + public func executeBatchUpdate(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) + } + } + + /// Closing A Database Connection + public func closeConnection() { + sqlite3_close(db) + } +} diff --git a/Sources/Database/SqliteError.swift b/Sources/Database/SqliteError.swift new file mode 100644 index 000000000..f5c7e2492 --- /dev/null +++ b/Sources/Database/SqliteError.swift @@ -0,0 +1,8 @@ +import Foundation + +public enum SQLiteError: Error { + case openDatabase(path: String) + case queryPrepare(statement: String) + case exec(error: String?) + case decodeString(index: Int32) +} diff --git a/Sources/Database/SqliteRow.swift b/Sources/Database/SqliteRow.swift new file mode 100644 index 000000000..a088bb551 --- /dev/null +++ b/Sources/Database/SqliteRow.swift @@ -0,0 +1,7 @@ +import Foundation + +public protocol SqliteRow { + /// SqliteRow initialization + /// - Parameter decoder: SqliteRowDecoder instance + init(decoder: SqliteRowDecoder) throws +} diff --git a/Sources/Database/SqliteRowDecoder.swift b/Sources/Database/SqliteRowDecoder.swift new file mode 100644 index 000000000..cbad01265 --- /dev/null +++ b/Sources/Database/SqliteRowDecoder.swift @@ -0,0 +1,29 @@ +import Foundation +import SQLite3 + +public class SqliteRowDecoder { + + private let statement: OpaquePointer? + + init(statement: OpaquePointer?) { + self.statement = statement + } + + /// Decode string from column at index + /// - Parameter index: Column index + /// - Returns: Decoded string + public func decodeString(at index: Int32) throws -> String { + guard let raw = sqlite3_column_text(statement, index) else { + throw SQLiteError.decodeString(index: index) + } + return String(cString: raw) + } + + /// Decode bool from column at index + /// - Parameter index: Column index + /// - Returns: Decoded bool + public func decodeBool(at index: Int32) throws -> Bool { + let string = try decodeString(at: index) + return (string as NSString).boolValue + } +} From db9a6271ac49213fae948825d732dd948d229510 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 27 Oct 2023 03:09:49 +0800 Subject: [PATCH 2/8] NotifyDatabase --- Package.swift | 2 +- Sources/Database/SQLiteQuery.swift | 25 +++++++ Sources/Database/Sqlite.swift | 2 +- Sources/Database/SqliteError.swift | 2 + Sources/Database/SqliteRow.swift | 7 +- Sources/Database/SqliteRowDecoder.swift | 22 ++++++ Sources/Database/SqliteRowEncoder.swift | 33 +++++++++ .../Client/Wallet/NotifyDatabase.swift | 70 +++++++++++++++++++ .../WalletConnectNotify/NotifyImports.swift | 1 + .../DataStructures/NotifySubscription.swift | 25 ++++++- 10 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 Sources/Database/SQLiteQuery.swift create mode 100644 Sources/Database/SqliteRowEncoder.swift create mode 100644 Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift diff --git a/Package.swift b/Package.swift index 052bd1772..f43771ef7 100644 --- a/Package.swift +++ b/Package.swift @@ -77,7 +77,7 @@ let package = Package( path: "Sources/Web3Wallet"), .target( name: "WalletConnectNotify", - dependencies: ["WalletConnectPairing", "WalletConnectPush", "WalletConnectIdentity", "WalletConnectSigner"], + dependencies: ["WalletConnectPairing", "WalletConnectPush", "WalletConnectIdentity", "WalletConnectSigner", "Database"], path: "Sources/WalletConnectNotify"), .target( name: "WalletConnectPush", diff --git a/Sources/Database/SQLiteQuery.swift b/Sources/Database/SQLiteQuery.swift new file mode 100644 index 000000000..9cfce38b9 --- /dev/null +++ b/Sources/Database/SQLiteQuery.swift @@ -0,0 +1,25 @@ +import Foundation + +public struct SqliteQuery { + + public static func replace(table: String, row: SqliteRow) -> String { + let encoder = row.encode() + + let arguments = encoder.values + .map { $0.argument } + .joined(separator: ", ") + + let values = encoder.values + .map { $0.value } + .joined(separator: ", ") + + return """ + REPLACE INTO \(table) (\(arguments)) + VALUES (\(values)) + """ + } + + public static func select(table: String) -> String { + return "SELECT * FROM \(table)" + } +} diff --git a/Sources/Database/Sqlite.swift b/Sources/Database/Sqlite.swift index e6f008cde..61b2cd8de 100644 --- a/Sources/Database/Sqlite.swift +++ b/Sources/Database/Sqlite.swift @@ -38,7 +38,7 @@ public final class Sqlite { /// One-Step query execution /// - Parameter sql: SQL query - public func executeBatchUpdate(sql: String) throws { + 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) } diff --git a/Sources/Database/SqliteError.swift b/Sources/Database/SqliteError.swift index f5c7e2492..cecf7dd89 100644 --- a/Sources/Database/SqliteError.swift +++ b/Sources/Database/SqliteError.swift @@ -5,4 +5,6 @@ public enum SQLiteError: Error { case queryPrepare(statement: String) case exec(error: String?) case decodeString(index: Int32) + case stringIsNotBase64 + case stringIsNotTimestamp } diff --git a/Sources/Database/SqliteRow.swift b/Sources/Database/SqliteRow.swift index a088bb551..f1a683083 100644 --- a/Sources/Database/SqliteRow.swift +++ b/Sources/Database/SqliteRow.swift @@ -1,7 +1,12 @@ import Foundation public protocol SqliteRow { - /// SqliteRow initialization + + /// SqliteRow initialization /// - Parameter decoder: SqliteRowDecoder instance init(decoder: SqliteRowDecoder) throws + + /// SqliteRow encoding + /// - Returns: SqliteRowEncoder instance + func encode() -> SqliteRowEncoder } diff --git a/Sources/Database/SqliteRowDecoder.swift b/Sources/Database/SqliteRowDecoder.swift index cbad01265..bd9bde816 100644 --- a/Sources/Database/SqliteRowDecoder.swift +++ b/Sources/Database/SqliteRowDecoder.swift @@ -26,4 +26,26 @@ public class SqliteRowDecoder { let string = try decodeString(at: index) return (string as NSString).boolValue } + + /// Decode codable object from column at index + /// - Parameter index: Column index + /// - Returns: Decoded codable object + public func decodeCodable(at index: Int32) throws -> T { + let string = try decodeString(at: index) + guard let data = Data(base64Encoded: string) else { + throw SQLiteError.stringIsNotBase64 + } + return try JSONDecoder().decode(T.self, from: data) + } + + /// Decode date from column at index + /// - Parameter index: Column index + /// - Returns: Decoded date + public func decodeDate(at index: Int32) throws -> Date { + let string = try decodeString(at: index) + guard let interval = TimeInterval(string) else { + throw SQLiteError.stringIsNotTimestamp + } + return Date(timeIntervalSince1970: interval) + } } diff --git a/Sources/Database/SqliteRowEncoder.swift b/Sources/Database/SqliteRowEncoder.swift new file mode 100644 index 000000000..9c7516b41 --- /dev/null +++ b/Sources/Database/SqliteRowEncoder.swift @@ -0,0 +1,33 @@ +import Foundation + +public struct SqliteRowEncoder { + struct Value { + let argument: String + let value: String + } + + var values: [Value] = [] + + public init() { } + + public mutating func encodeString(_ value: String, for argument: String) { + let value = Value(argument: argument, value: value) + values.append(value) + } + + public mutating func encodeDate(_ value: Date, for argument: String) { + let value = Value(argument: argument, value: String(value.timeIntervalSince1970)) + values.append(value) + } + + public mutating func encodeCodable(_ value: T, for argument: String) { + let data = try! JSONEncoder().encode(value) + let value = Value(argument: argument, value: data.base64EncodedString()) + values.append(value) + } + + public mutating func encodeBool(_ value: Bool, for argument: String) { + let value = Value(argument: argument, value: String(value)) + values.append(value) + } +} diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift new file mode 100644 index 000000000..8085cdcdc --- /dev/null +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift @@ -0,0 +1,70 @@ +import Foundation +import Database + +final class NotifyDatabase { + + enum Table { + static let subscriptions = "NotifySubscription" + } + + private let appGroup: String + private let database: String + private let sqlite: Sqlite + + init(appGroup: String, database: String, sqlite: Sqlite) { + self.appGroup = appGroup + self.database = database + self.sqlite = sqlite + + prepareDatabase() + } + + func save(subscription: NotifySubscription) throws { + try sqlite.openDatabase(path: path) + defer { sqlite.closeConnection() } + + let sql = SqliteQuery.replace(table: Table.subscriptions, row: subscription) + try sqlite.execute(sql: sql) + + sqlite.closeConnection() + } + + func getSubscription(topic: String) throws -> NotifySubscription? { + try sqlite.openDatabase(path: path) + defer { sqlite.closeConnection() } + + let sql = SqliteQuery.select(table: Table.subscriptions) + let subscriptions: [NotifySubscription] = try sqlite.query(sql: sql) + return subscriptions.first(where: { $0.topic == topic }) + } +} + +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() { + defer { sqlite.closeConnection() } + try? sqlite.openDatabase(path: path) + try? sqlite.execute(sql: """ + CREATE TABLE NotifySubscription ( + topic TEXT PRIMARY KEY, + account TEXT NOT NULL, + relay TEXT NOT NULL, + metadata TEXT NOT NULL, + scope TEXT NOT NULL, + expiry TEXT NOT NULL, + symKey TEXT NOT NULL + ); + """) + } +} diff --git a/Sources/WalletConnectNotify/NotifyImports.swift b/Sources/WalletConnectNotify/NotifyImports.swift index 74fcfa250..43b543236 100644 --- a/Sources/WalletConnectNotify/NotifyImports.swift +++ b/Sources/WalletConnectNotify/NotifyImports.swift @@ -3,4 +3,5 @@ @_exported import WalletConnectPush @_exported import WalletConnectIdentity @_exported import WalletConnectSigner +@_exported import Database #endif diff --git a/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift b/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift index cb4982555..5a5701d24 100644 --- a/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift +++ b/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift @@ -1,6 +1,7 @@ import Foundation +import Database -public struct NotifySubscription: DatabaseObject { +public struct NotifySubscription: DatabaseObject, SqliteRow { public let topic: String public let account: Account public let relay: RelayProtocolOptions @@ -13,6 +14,28 @@ public struct NotifySubscription: DatabaseObject { public var databaseId: String { return topic } + + public init(decoder: SqliteRowDecoder) throws { + self.topic = try decoder.decodeString(at: 0) + self.account = try Account(decoder.decodeString(at: 1))! + self.relay = try decoder.decodeCodable(at: 2) + self.metadata = try decoder.decodeCodable(at: 3) + self.scope = try decoder.decodeCodable(at: 4) + self.expiry = try decoder.decodeDate(at: 5) + self.symKey = try decoder.decodeString(at: 6) + } + + public func encode() -> SqliteRowEncoder { + var encoder = SqliteRowEncoder() + encoder.encodeString(topic, for: "topic") + encoder.encodeString(account.absoluteString, for: "account") + encoder.encodeCodable(relay, for: "relay") + encoder.encodeCodable(metadata, for: "metadata") + encoder.encodeCodable(scope, for: "scope") + encoder.encodeDate(expiry, for: "expiry") + encoder.encodeString(symKey, for: "symKey") + return encoder + } } public struct ScopeValue: Codable, Equatable { From 7a9519c3599164a6da94afb0065776508f6753fa Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Mon, 30 Oct 2023 18:45:01 +0800 Subject: [PATCH 3/8] SQlite for subscriptions --- Sources/Database/SQLiteQuery.swift | 39 +++++++-- Sources/Database/Sqlite.swift | 4 +- .../Client/Wallet/NotifyAccountProvider.swift | 2 +- .../Client/Wallet/NotifyClient.swift | 5 +- .../Client/Wallet/NotifyClientFactory.swift | 5 +- .../Client/Wallet/NotifyDatabase.swift | 87 ++++++++++++++----- .../Client/Wallet/NotifyStorage.swift | 59 +++++++------ .../DeleteNotifySubscriptionRequester.swift | 2 +- ...ubscriptionsChangedRequestSubscriber.swift | 4 +- .../NotifyUpdateRequester.swift | 2 +- ...WatchSubscriptionsResponseSubscriber.swift | 4 +- .../DataStructures/NotifySubscription.swift | 24 +++++ 12 files changed, 164 insertions(+), 73 deletions(-) diff --git a/Sources/Database/SQLiteQuery.swift b/Sources/Database/SQLiteQuery.swift index 9cfce38b9..33447c371 100644 --- a/Sources/Database/SQLiteQuery.swift +++ b/Sources/Database/SQLiteQuery.swift @@ -2,24 +2,45 @@ import Foundation public struct SqliteQuery { - public static func replace(table: String, row: SqliteRow) -> String { - let encoder = row.encode() + public static func replace(table: String, rows: [SqliteRow]) throws -> String { + var values: [String] = [] - let arguments = encoder.values + for row in rows { + values.append(row.encode().values + .map { "'\($0.value)'" } + .joined(separator: ", ")) + } + + guard let first = rows.first else { + throw Errors.rowsNotFound + } + + let formattedArguments = first.encode().values .map { $0.argument } .joined(separator: ", ") - let values = encoder.values - .map { $0.value } - .joined(separator: ", ") + let formattedValues = values + .map { "(\($0))" } + .joined(separator: ",\n") return """ - REPLACE INTO \(table) (\(arguments)) - VALUES (\(values)) + REPLACE INTO \(table) (\(formattedArguments)) VALUES + \(formattedValues); """ } public static func select(table: String) -> String { - return "SELECT * FROM \(table)" + return "SELECT * FROM \(table);" + } + + public static func delete(table: String, where argument: String, equals value: String) -> String { + return "DELETE FROM \(table) WHERE \(argument) = '\(value)';" + } +} + +extension SqliteQuery { + + enum Errors: Error { + case rowsNotFound } } diff --git a/Sources/Database/Sqlite.swift b/Sources/Database/Sqlite.swift index 61b2cd8de..4ffa53143 100644 --- a/Sources/Database/Sqlite.swift +++ b/Sources/Database/Sqlite.swift @@ -5,10 +5,12 @@ public final class Sqlite { private var db: OpaquePointer? + public init() { } + /// Opening A New Database Connection /// - Parameter path: Path to database public func openDatabase(path: String) throws { - guard sqlite3_open(path, &db) == SQLITE_OK else { + 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? diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyAccountProvider.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyAccountProvider.swift index 8a702fabd..48df9c09c 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyAccountProvider.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyAccountProvider.swift @@ -5,7 +5,7 @@ final class NotifyAccountProvider { case currentAccountNotFound } - private var currentAccount: Account? + private(set) var currentAccount: Account? func setAccount(_ account: Account) { self.currentAccount = account diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift index b4e04c5be..1da1f0629 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift @@ -84,7 +84,7 @@ public class NotifyClient { public func unregister(account: Account) async throws { try await identityService.unregister(account: account) notifyWatcherAgreementKeysProvider.removeAgreement(account: account) - notifyStorage.clearDatabase(account: account) + try notifyStorage.clearDatabase(account: account) notifyAccountProvider.logout() subscriptionWatcher.stop() } @@ -102,7 +102,8 @@ public class NotifyClient { } public func getActiveSubscriptions(account: Account) -> [NotifySubscription] { - return notifyStorage.getSubscriptions(account: account) + // TODO: Handle or remove error + return (try? notifyStorage.getSubscriptions(account: account)) ?? [] } public func getMessageHistory(topic: String) -> [NotifyMessageRecord] { diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift index 9d0e41f4a..6e50ece69 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift @@ -12,6 +12,7 @@ public struct NotifyClientFactory { return NotifyClientFactory.create( projectId: projectId, keyserverURL: keyserverURL, + groupIdentifier: groupIdentifier, logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychainStorage, @@ -28,6 +29,7 @@ public struct NotifyClientFactory { static func create( projectId: String, keyserverURL: URL, + groupIdentifier: String, logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, @@ -43,7 +45,8 @@ public struct NotifyClientFactory { let subscriptionStore = KeyedDatabase(storage: keyValueStorage, identifier: NotifyStorageIdntifiers.notifySubscription) let messagesStore = KeyedDatabase(storage: keyValueStorage, identifier: NotifyStorageIdntifiers.notifyMessagesRecords) let notifyAccountProvider = NotifyAccountProvider() - let notifyStorage = NotifyStorage(subscriptionStore: subscriptionStore, messagesStore: messagesStore, accountProvider: notifyAccountProvider) + let database = NotifyDatabase(appGroup: groupIdentifier, database: "notify.db", sqlite: Sqlite(), logger: logger) + let notifyStorage = NotifyStorage(database: database, subscriptionStore: subscriptionStore, messagesStore: messagesStore, 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) let webDidResolver = NotifyWebDidResolver() diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift index 8085cdcdc..28d83e7a5 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift @@ -1,5 +1,6 @@ import Foundation import Database +import Combine final class NotifyDatabase { @@ -10,32 +11,52 @@ final class NotifyDatabase { private let appGroup: String private let database: String private let sqlite: Sqlite + private let logger: ConsoleLogging - init(appGroup: String, database: String, sqlite: Sqlite) { + var onSubscriptionsUpdate: (() throws -> Void)? + + init(appGroup: String, database: String, sqlite: Sqlite, logger: ConsoleLogging) { self.appGroup = appGroup self.database = database self.sqlite = sqlite + self.logger = logger prepareDatabase() } func save(subscription: NotifySubscription) throws { - try sqlite.openDatabase(path: path) - defer { sqlite.closeConnection() } - - let sql = SqliteQuery.replace(table: Table.subscriptions, row: subscription) - try sqlite.execute(sql: sql) + try save(subscriptions: [subscription]) + } - sqlite.closeConnection() + func save(subscriptions: [NotifySubscription]) throws { + let sql = try SqliteQuery.replace(table: Table.subscriptions, rows: subscriptions) + try execute(sql: sql) + try onSubscriptionsUpdate?() } func getSubscription(topic: String) throws -> NotifySubscription? { - try sqlite.openDatabase(path: path) - defer { sqlite.closeConnection() } + return try getAllSubscriptions().first(where: { $0.topic == topic }) + } + func getAllSubscriptions() throws -> [NotifySubscription] { let sql = SqliteQuery.select(table: Table.subscriptions) - let subscriptions: [NotifySubscription] = try sqlite.query(sql: sql) - return subscriptions.first(where: { $0.topic == topic }) + return try query(sql: sql) + } + + func getSubscriptions(account: Account) throws -> [NotifySubscription] { + return try getAllSubscriptions().filter { $0.account == account } + } + + func deleteSubscription(topic: String) throws { + let sql = SqliteQuery.delete(table: Table.subscriptions, where: "topic", equals: topic) + try execute(sql: sql) + try onSubscriptionsUpdate?() + } + + func deleteSubscription(account: Account) throws { + let sql = SqliteQuery.delete(table: Table.subscriptions, where: "account", equals: account.absoluteString) + try execute(sql: sql) + try onSubscriptionsUpdate?() } } @@ -53,18 +74,38 @@ private extension NotifyDatabase { } func prepareDatabase() { + do { + defer { sqlite.closeConnection() } + try sqlite.openDatabase(path: path) + try sqlite.execute(sql: """ + CREATE TABLE IF NOT EXISTS NotifySubscription ( + topic TEXT PRIMARY KEY, + account TEXT NOT NULL, + relay TEXT NOT NULL, + metadata TEXT NOT NULL, + scope TEXT NOT NULL, + expiry TEXT NOT NULL, + symKey TEXT NOT NULL, + appAuthenticationKey TEXT NOT NULL + ); + """) + logger.debug("SQlite database created at path \(path)") + } catch { + logger.error("SQlite database creation error: \(error.localizedDescription)") + } + } + + func execute(sql: String) throws { + try sqlite.openDatabase(path: path) defer { sqlite.closeConnection() } - try? sqlite.openDatabase(path: path) - try? sqlite.execute(sql: """ - CREATE TABLE NotifySubscription ( - topic TEXT PRIMARY KEY, - account TEXT NOT NULL, - relay TEXT NOT NULL, - metadata TEXT NOT NULL, - scope TEXT NOT NULL, - expiry TEXT NOT NULL, - symKey TEXT NOT NULL - ); - """) + + try sqlite.execute(sql: sql) + } + + func query(sql: String) throws -> [T] { + try sqlite.openDatabase(path: path) + 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 ab873dcf1..d51b5a88b 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift @@ -3,19 +3,21 @@ import Combine protocol NotifyStoring { func getAllSubscriptions() -> [NotifySubscription] - func getSubscriptions(account: Account) -> [NotifySubscription] - func getSubscription(topic: String) -> NotifySubscription? - func setSubscription(_ subscription: NotifySubscription) async throws - func deleteSubscription(topic: String) async throws - func clearDatabase(account: Account) + func getSubscriptions(account: Account) throws -> [NotifySubscription] + func getSubscription(topic: String) throws -> 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 { private var publishers = Set() - private let subscriptionStore: KeyedDatabase private let messagesStore: KeyedDatabase + private let database: NotifyDatabase private let newSubscriptionSubject = PassthroughSubject() private let updateSubscriptionSubject = PassthroughSubject() @@ -41,8 +43,8 @@ final class NotifyStorage: NotifyStoring { return subscriptionsSubject.eraseToAnyPublisher() } - init(subscriptionStore: KeyedDatabase, messagesStore: KeyedDatabase, accountProvider: NotifyAccountProvider) { - self.subscriptionStore = subscriptionStore + init(database: NotifyDatabase, subscriptionStore: KeyedDatabase, messagesStore: KeyedDatabase, accountProvider: NotifyAccountProvider) { + self.database = database self.messagesStore = messagesStore self.accountProvider = accountProvider @@ -52,45 +54,42 @@ final class NotifyStorage: NotifyStoring { // MARK: Subscriptions func getAllSubscriptions() -> [NotifySubscription] { - return subscriptionStore.getAll() + return (try? database.getAllSubscriptions()) ?? [] } - func getSubscriptions(account: Account) -> [NotifySubscription] { - return subscriptionStore.getAll(for: account.absoluteString) + func getSubscriptions(account: Account) throws -> [NotifySubscription] { + return try database.getSubscriptions(account: account) } - func getSubscription(topic: String) -> NotifySubscription? { - return subscriptionStore.getAll().first(where: { $0.topic == topic }) + func getSubscription(topic: String) throws -> NotifySubscription? { + return try database.getSubscription(topic: topic) } - func setSubscription(_ subscription: NotifySubscription) { - subscriptionStore.set(element: subscription, for: subscription.account.absoluteString) + func setSubscription(_ subscription: NotifySubscription) throws { + try database.save(subscription: subscription) newSubscriptionSubject.send(subscription) } - func replaceAllSubscriptions(_ subscriptions: [NotifySubscription], account: Account) { - subscriptionStore.replace(elements: subscriptions, for: account.absoluteString) + func replaceAllSubscriptions(_ subscriptions: [NotifySubscription]) throws { + try database.save(subscriptions: subscriptions) } func deleteSubscription(topic: String) throws { - guard let subscription = getSubscription(topic: topic) else { - throw Errors.subscriptionNotFound - } - subscriptionStore.delete(id: topic, for: subscription.account.absoluteString) + try database.deleteSubscription(topic: topic) deleteSubscriptionSubject.send(topic) } - func clearDatabase(account: Account) { - for subscription in getSubscriptions(account: account) { + func clearDatabase(account: Account) throws { + for subscription in try getSubscriptions(account: account) { deleteMessages(topic: subscription.topic) } - subscriptionStore.deleteAll(for: account.absoluteString) + try database.deleteSubscription(account: account) } - func updateSubscription(_ subscription: NotifySubscription, scope: [String: ScopeValue], expiry: UInt64) { + func updateSubscription(_ subscription: NotifySubscription, scope: [String: ScopeValue], expiry: UInt64) throws { 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, appAuthenticationKey: subscription.appAuthenticationKey) - subscriptionStore.set(element: updated, for: updated.account.absoluteString) + let updated = NotifySubscription(subscription: subscription, scope: scope, expiry: expiry) + try database.save(subscription: updated) updateSubscriptionSubject.send(updated) } @@ -132,9 +131,9 @@ private extension NotifyStorage { messagesSubject.send(messagesStore.getAll()) } - subscriptionStore.onUpdate = { [unowned self] in - guard let account = try? accountProvider.getCurrentAccount() else { return } - subscriptionsSubject.send(getSubscriptions(account: account)) + database.onSubscriptionsUpdate = { [unowned self] in + let account = try accountProvider.getCurrentAccount() + subscriptionsSubject.send(try getSubscriptions(account: account)) } } } diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/DeleteNotifySubscriptionRequester.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/DeleteNotifySubscriptionRequester.swift index a94e86420..ae3cc6977 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/DeleteNotifySubscriptionRequester.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/DeleteNotifySubscriptionRequester.swift @@ -30,7 +30,7 @@ class DeleteNotifySubscriptionRequester { func delete(topic: String) async throws { logger.debug("Will delete notify subscription") - guard let subscription = notifyStorage.getSubscription(topic: topic) + guard let subscription = try notifyStorage.getSubscription(topic: topic) else { throw Errors.notifySubscriptionNotFound} let protocolMethod = NotifyDeleteProtocolMethod() diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifySubscriptionsChanged/NotifySubscriptionsChangedRequestSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifySubscriptionsChanged/NotifySubscriptionsChangedRequestSubscriber.swift index 18c8bbc31..91f299e59 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifySubscriptionsChanged/NotifySubscriptionsChangedRequestSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifySubscriptionsChanged/NotifySubscriptionsChangedRequestSubscriber.swift @@ -51,7 +51,7 @@ class NotifySubscriptionsChangedRequestSubscriber { // TODO: varify signature with notify server diddoc authentication key - let oldSubscriptions = notifyStorage.getSubscriptions(account: account) + let oldSubscriptions = try notifyStorage.getSubscriptions(account: account) let newSubscriptions = try await notifySubscriptionsBuilder.buildSubscriptions(jwtPayload.subscriptions) subscriptionChangedSubject.send(newSubscriptions) @@ -63,7 +63,7 @@ class NotifySubscriptionsChangedRequestSubscriber { logger.debug("Received: \(newSubscriptions.count), changed: \(subscriptions.count)") if subscriptions.count > 0 { - notifyStorage.replaceAllSubscriptions(newSubscriptions, account: account) + try notifyStorage.replaceAllSubscriptions(newSubscriptions) for subscription in newSubscriptions { let symKey = try SymmetricKey(hex: subscription.symKey) diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift index 104c5d31a..cf9a44f02 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift @@ -35,7 +35,7 @@ class NotifyUpdateRequester: NotifyUpdateRequesting { func update(topic: String, scope: Set) async throws { logger.debug("NotifyUpdateRequester: updating subscription for topic: \(topic)") - guard let subscription = notifyStorage.getSubscription(topic: topic) else { throw Errors.noSubscriptionForGivenTopic } + guard let subscription = try notifyStorage.getSubscription(topic: topic) else { throw Errors.noSubscriptionForGivenTopic } let dappAuthenticationKey = try DIDKey(did: subscription.appAuthenticationKey) diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsResponseSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsResponseSubscriber.swift index 55323843e..541c0279e 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsResponseSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsResponseSubscriber.swift @@ -40,7 +40,7 @@ class NotifyWatchSubscriptionsResponseSubscriber { let account = watchSubscriptionPayloadRequest.subscriptionAccount // TODO: varify signature with notify server diddoc authentication key - let oldSubscriptions = notifyStorage.getSubscriptions(account: account) + let oldSubscriptions = try notifyStorage.getSubscriptions(account: account) let newSubscriptions = try await notifySubscriptionsBuilder.buildSubscriptions(responsePayload.subscriptions) try Task.checkCancellation() @@ -51,7 +51,7 @@ class NotifyWatchSubscriptionsResponseSubscriber { if subscriptions.count > 0 { // TODO: unsubscribe for oldSubscriptions topics that are not included in new subscriptions - notifyStorage.replaceAllSubscriptions(newSubscriptions, account: account) + try notifyStorage.replaceAllSubscriptions(newSubscriptions) for subscription in newSubscriptions { let symKey = try SymmetricKey(hex: subscription.symKey) diff --git a/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift b/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift index 5a5701d24..9a5ac3609 100644 --- a/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift +++ b/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift @@ -23,6 +23,7 @@ public struct NotifySubscription: DatabaseObject, SqliteRow { self.scope = try decoder.decodeCodable(at: 4) self.expiry = try decoder.decodeDate(at: 5) self.symKey = try decoder.decodeString(at: 6) + self.appAuthenticationKey = try decoder.decodeString(at: 7) } public func encode() -> SqliteRowEncoder { @@ -34,8 +35,31 @@ public struct NotifySubscription: DatabaseObject, SqliteRow { encoder.encodeCodable(scope, for: "scope") encoder.encodeDate(expiry, for: "expiry") encoder.encodeString(symKey, for: "symKey") + encoder.encodeString(appAuthenticationKey, for: "appAuthenticationKey") return encoder } + + init(topic: String, account: Account, relay: RelayProtocolOptions, metadata: AppMetadata, scope: [String : ScopeValue], expiry: Date, symKey: String, appAuthenticationKey: String) { + self.topic = topic + self.account = account + self.relay = relay + self.metadata = metadata + self.scope = scope + self.expiry = expiry + self.symKey = symKey + self.appAuthenticationKey = appAuthenticationKey + } + + init(subscription: NotifySubscription, scope: [String : ScopeValue], expiry: Date) { + self.topic = subscription.topic + self.account = subscription.account + self.relay = subscription.relay + self.metadata = subscription.metadata + self.symKey = subscription.symKey + self.appAuthenticationKey = subscription.appAuthenticationKey + self.scope = scope + self.expiry = expiry + } } public struct ScopeValue: Codable, Equatable { From c3677b4dd2d3e19d5b78d001ae283db6f8653c30 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Thu, 2 Nov 2023 00:38:32 +0800 Subject: [PATCH 4/8] SQlite for Messages --- .../ApplicationLayer/LoggingService.swift | 2 +- Sources/Database/SQLiteQuery.swift | 4 + .../Client/Wallet/NotifyClient.swift | 5 +- .../Client/Wallet/NotifyClientFactory.swift | 4 +- .../Client/Wallet/NotifyDatabase.swift | 74 +++++++++++++++++-- .../Client/Wallet/NotifyMessageRecord.swift | 37 +++++++++- .../Client/Wallet/NotifyStorage.swift | 40 +++++----- .../DeleteNotifySubscriptionRequester.swift | 4 +- .../NotifyMessageSubscriber.swift | 2 +- .../NotifySubscribeRequester.swift | 2 - .../NotifyStorageIdntifiers.swift | 8 -- .../Types/DataStructures/NotifyMessage.swift | 4 +- 12 files changed, 134 insertions(+), 52 deletions(-) delete mode 100644 Sources/WalletConnectNotify/NotifyStorageIdntifiers.swift diff --git a/Example/WalletApp/ApplicationLayer/LoggingService.swift b/Example/WalletApp/ApplicationLayer/LoggingService.swift index e7a74cb9b..b03f7002b 100644 --- a/Example/WalletApp/ApplicationLayer/LoggingService.swift +++ b/Example/WalletApp/ApplicationLayer/LoggingService.swift @@ -49,7 +49,7 @@ final class LoggingService { SentrySDK.capture(error: LoggingError.networking(log.aggregated)) case .warn(let log): // Example of setting level to warning - var event = Event(level: .warning) + let event = Event(level: .warning) event.message = SentryMessage(formatted: log.aggregated) SentrySDK.capture(event: event) default: diff --git a/Sources/Database/SQLiteQuery.swift b/Sources/Database/SQLiteQuery.swift index 33447c371..9afa8c66a 100644 --- a/Sources/Database/SQLiteQuery.swift +++ b/Sources/Database/SQLiteQuery.swift @@ -33,6 +33,10 @@ public struct SqliteQuery { return "SELECT * FROM \(table);" } + public static func select(table: String, where argument: String, equals value: String) -> String { + return "SELECT * FROM \(table) WHERE \(argument) = '\(value);" + } + public static func delete(table: String, where argument: String, equals value: String) -> String { return "DELETE FROM \(table) WHERE \(argument) = '\(value)';" } diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift index 1da1f0629..bba5af964 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift @@ -102,8 +102,7 @@ public class NotifyClient { } public func getActiveSubscriptions(account: Account) -> [NotifySubscription] { - // TODO: Handle or remove error - return (try? notifyStorage.getSubscriptions(account: account)) ?? [] + return notifyStorage.getSubscriptions(account: account) } public func getMessageHistory(topic: String) -> [NotifyMessageRecord] { @@ -115,7 +114,7 @@ public class NotifyClient { } public func deleteNotifyMessage(id: String) { - notifyStorage.deleteMessage(id: id) + try? notifyStorage.deleteMessage(id: id) } public func register(deviceToken: Data) async throws { diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift index 6e50ece69..3e093c089 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift @@ -42,11 +42,9 @@ public struct NotifyClientFactory { explorerHost: String ) -> NotifyClient { let kms = KeyManagementService(keychain: keychainStorage) - let subscriptionStore = KeyedDatabase(storage: keyValueStorage, identifier: NotifyStorageIdntifiers.notifySubscription) - let messagesStore = KeyedDatabase(storage: keyValueStorage, identifier: NotifyStorageIdntifiers.notifyMessagesRecords) let notifyAccountProvider = NotifyAccountProvider() let database = NotifyDatabase(appGroup: groupIdentifier, database: "notify.db", sqlite: Sqlite(), logger: logger) - let notifyStorage = NotifyStorage(database: database, subscriptionStore: subscriptionStore, messagesStore: messagesStore, accountProvider: notifyAccountProvider) + 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) let webDidResolver = NotifyWebDidResolver() diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift index 28d83e7a5..19134981e 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift @@ -6,6 +6,7 @@ final class NotifyDatabase { enum Table { static let subscriptions = "NotifySubscription" + static let messages = "NotifyMessage" } private let appGroup: String @@ -14,6 +15,7 @@ final class NotifyDatabase { private let logger: ConsoleLogging var onSubscriptionsUpdate: (() throws -> Void)? + var onMessagesUpdate: (() throws -> Void)? init(appGroup: String, database: String, sqlite: Sqlite, logger: ConsoleLogging) { self.appGroup = appGroup @@ -24,6 +26,8 @@ final class NotifyDatabase { prepareDatabase() } + // MARK: - NotifySubscriptions + func save(subscription: NotifySubscription) throws { try save(subscriptions: [subscription]) } @@ -34,17 +38,22 @@ final class NotifyDatabase { try onSubscriptionsUpdate?() } - func getSubscription(topic: String) throws -> NotifySubscription? { - return try getAllSubscriptions().first(where: { $0.topic == topic }) + func getSubscription(topic: String) -> NotifySubscription? { + let sql = SqliteQuery.select(table: Table.subscriptions, where: "topic", equals: topic) + let subscriptions: [NotifySubscription]? = try? query(sql: sql) + return subscriptions?.first } - func getAllSubscriptions() throws -> [NotifySubscription] { + func getAllSubscriptions() -> [NotifySubscription] { let sql = SqliteQuery.select(table: Table.subscriptions) - return try query(sql: sql) + let subscriptions: [NotifySubscription]? = try? query(sql: sql) + return subscriptions ?? [] } - func getSubscriptions(account: Account) throws -> [NotifySubscription] { - return try getAllSubscriptions().filter { $0.account == account } + func getSubscriptions(account: Account) -> [NotifySubscription] { + let sql = SqliteQuery.select(table: Table.subscriptions, where: "account", equals: account.absoluteString) + let subscriptions: [NotifySubscription]? = try? query(sql: sql) + return subscriptions ?? [] } func deleteSubscription(topic: String) throws { @@ -58,6 +67,42 @@ final class NotifyDatabase { try execute(sql: sql) try onSubscriptionsUpdate?() } + + // MARK: - NotifyMessageRecord + + func getAllMessages() -> [NotifyMessageRecord] { + let sql = SqliteQuery.select(table: Table.messages) + let messages: [NotifyMessageRecord]? = try? query(sql: sql) + return messages ?? [] + } + + func getMessages(topic: String) -> [NotifyMessageRecord] { + let sql = SqliteQuery.select(table: Table.messages, where: "topic", equals: topic) + let messages: [NotifyMessageRecord]? = try? query(sql: sql) + return messages ?? [] + } + + func deleteMessages(topic: String) throws { + let sql = SqliteQuery.delete(table: Table.messages, where: "topic", equals: topic) + try execute(sql: sql) + try onMessagesUpdate?() + } + + func deleteMessage(id: String) throws { + let sql = SqliteQuery.delete(table: Table.messages, where: "id", equals: id) + try execute(sql: sql) + try onMessagesUpdate?() + } + + func save(message: NotifyMessageRecord) throws { + try save(messages: [message]) + } + + func save(messages: [NotifyMessageRecord]) throws { + let sql = try SqliteQuery.replace(table: Table.messages, rows: messages) + try execute(sql: sql) + try onMessagesUpdate?() + } } private extension NotifyDatabase { @@ -77,8 +122,9 @@ private extension NotifyDatabase { do { defer { sqlite.closeConnection() } try sqlite.openDatabase(path: path) + try sqlite.execute(sql: """ - CREATE TABLE IF NOT EXISTS NotifySubscription ( + CREATE TABLE IF NOT EXISTS \(Table.subscriptions) ( topic TEXT PRIMARY KEY, account TEXT NOT NULL, relay TEXT NOT NULL, @@ -89,6 +135,20 @@ private extension NotifyDatabase { appAuthenticationKey TEXT NOT NULL ); """) + + try sqlite.execute(sql: """ + CREATE TABLE IF NOT EXISTS \(Table.messages) ( + id TEXT PRIMARY KEY, + topic TEXT NOT NULL, + title TEXT NOT NULL, + body TEXT NOT NULL, + icon TEXT NOT NULL, + url TEXT NOT NULL, + type TEXT NOT NULL, + publishedAt TEXT NOT NULL, + ); + """) + logger.debug("SQlite database created at path \(path)") } catch { logger.error("SQlite database creation error: \(error.localizedDescription)") diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyMessageRecord.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyMessageRecord.swift index a9431587a..d12331d34 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyMessageRecord.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyMessageRecord.swift @@ -1,6 +1,6 @@ import Foundation -public struct NotifyMessageRecord: Codable, Equatable, DatabaseObject { +public struct NotifyMessageRecord: Codable, Equatable, SqliteRow { public let id: String public let topic: String public let message: NotifyMessage @@ -9,4 +9,39 @@ public struct NotifyMessageRecord: Codable, Equatable, DatabaseObject { public var databaseId: String { return id } + + public init(id: String, topic: String, message: NotifyMessage, publishedAt: Date) { + self.id = id + self.topic = topic + self.message = message + self.publishedAt = publishedAt + } + + public init(decoder: SqliteRowDecoder) throws { + self.id = try decoder.decodeString(at: 0) + self.topic = try decoder.decodeString(at: 1) + + self.message = NotifyMessage( + title: try decoder.decodeString(at: 2), + body: try decoder.decodeString(at: 3), + icon: try decoder.decodeString(at: 4), + url: try decoder.decodeString(at: 5), + type: try decoder.decodeString(at: 6) + ) + + self.publishedAt = try decoder.decodeDate(at: 7) + } + + public func encode() -> SqliteRowEncoder { + var encoder = SqliteRowEncoder() + encoder.encodeString(id, for: "id") + encoder.encodeString(topic, for: "topic") + encoder.encodeString(message.title, for: "title") + encoder.encodeString(message.body, for: "body") + encoder.encodeString(message.icon, for: "icon") + encoder.encodeString(message.url, for: "url") + encoder.encodeString(message.type, for: "type") + encoder.encodeDate(publishedAt, for: "publishedAt") + return encoder + } } diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift index d51b5a88b..92bac470f 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift @@ -16,7 +16,6 @@ final class NotifyStorage: NotifyStoring { private var publishers = Set() - private let messagesStore: KeyedDatabase private let database: NotifyDatabase private let newSubscriptionSubject = PassthroughSubject() @@ -43,9 +42,8 @@ final class NotifyStorage: NotifyStoring { return subscriptionsSubject.eraseToAnyPublisher() } - init(database: NotifyDatabase, subscriptionStore: KeyedDatabase, messagesStore: KeyedDatabase, accountProvider: NotifyAccountProvider) { + init(database: NotifyDatabase, accountProvider: NotifyAccountProvider) { self.database = database - self.messagesStore = messagesStore self.accountProvider = accountProvider setupSubscriptions() @@ -54,15 +52,15 @@ final class NotifyStorage: NotifyStoring { // MARK: Subscriptions func getAllSubscriptions() -> [NotifySubscription] { - return (try? database.getAllSubscriptions()) ?? [] + return database.getAllSubscriptions() } - func getSubscriptions(account: Account) throws -> [NotifySubscription] { - return try database.getSubscriptions(account: account) + func getSubscriptions(account: Account) -> [NotifySubscription] { + return database.getSubscriptions(account: account) } - func getSubscription(topic: String) throws -> NotifySubscription? { - return try database.getSubscription(topic: topic) + func getSubscription(topic: String) -> NotifySubscription? { + return database.getSubscription(topic: topic) } func setSubscription(_ subscription: NotifySubscription) throws { @@ -80,8 +78,8 @@ final class NotifyStorage: NotifyStoring { } func clearDatabase(account: Account) throws { - for subscription in try getSubscriptions(account: account) { - deleteMessages(topic: subscription.topic) + for subscription in getSubscriptions(account: account) { + try database.deleteMessages(topic: subscription.topic) } try database.deleteSubscription(account: account) } @@ -102,21 +100,19 @@ final class NotifyStorage: NotifyStoring { } func getMessages(topic: String) -> [NotifyMessageRecord] { - return messagesStore.getAll(for: topic) - .sorted{$0.publishedAt > $1.publishedAt} + return database.getMessages(topic: topic) } - func deleteMessages(topic: String) { - messagesStore.deleteAll(for: topic) + func deleteMessages(topic: String) throws { + try database.deleteMessages(topic: topic) } - func deleteMessage(id: String) { - guard let result = messagesStore.find(id: id) else { return } - messagesStore.delete(id: id, for: result.key) + func deleteMessage(id: String) throws { + try database.deleteMessage(id: id) } - func setMessage(_ record: NotifyMessageRecord) { - messagesStore.set(element: record, for: record.topic) + func setMessage(_ message: NotifyMessageRecord) throws { + try database.save(message: message) } } @@ -127,13 +123,13 @@ private extension NotifyStorage { } func setupSubscriptions() { - messagesStore.onUpdate = { [unowned self] in - messagesSubject.send(messagesStore.getAll()) + database.onMessagesUpdate = { [unowned self] in + messagesSubject.send(database.getAllMessages()) } database.onSubscriptionsUpdate = { [unowned self] in let account = try accountProvider.getCurrentAccount() - subscriptionsSubject.send(try getSubscriptions(account: account)) + subscriptionsSubject.send(getSubscriptions(account: account)) } } } diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/DeleteNotifySubscriptionRequester.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/DeleteNotifySubscriptionRequester.swift index ae3cc6977..b9c86009c 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/DeleteNotifySubscriptionRequester.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/DeleteNotifySubscriptionRequester.swift @@ -30,7 +30,7 @@ class DeleteNotifySubscriptionRequester { func delete(topic: String) async throws { logger.debug("Will delete notify subscription") - guard let subscription = try notifyStorage.getSubscription(topic: topic) + guard let subscription = notifyStorage.getSubscription(topic: topic) else { throw Errors.notifySubscriptionNotFound} let protocolMethod = NotifyDeleteProtocolMethod() @@ -47,7 +47,7 @@ class DeleteNotifySubscriptionRequester { try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) try notifyStorage.deleteSubscription(topic: topic) - notifyStorage.deleteMessages(topic: topic) + try notifyStorage.deleteMessages(topic: topic) networkingInteractor.unsubscribe(topic: topic) diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift index 2ebd5125f..9b9a2cd8f 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift @@ -38,7 +38,7 @@ 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) - notifyStorage.setMessage(record) + try notifyStorage.setMessage(record) notifyMessagePublisherSubject.send(record) let receiptPayload = NotifyMessageReceiptPayload( diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeRequester.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeRequester.swift index 32a875b43..1907d640e 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeRequester.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeRequester.swift @@ -36,8 +36,6 @@ class NotifySubscribeRequester { logger.debug("Subscribing for Notify, dappUrl: \(appDomain)") - let config = await notifyConfigProvider.resolveNotifyConfig(appDomain: appDomain) - let didDoc = try await webDidResolver.resolveDidDoc(appDomain: appDomain) let peerPublicKey = try webDidResolver.resolveAgreementKey(didDoc: didDoc) let subscribeTopic = peerPublicKey.rawRepresentation.sha256().toHexString() diff --git a/Sources/WalletConnectNotify/NotifyStorageIdntifiers.swift b/Sources/WalletConnectNotify/NotifyStorageIdntifiers.swift deleted file mode 100644 index b68272b25..000000000 --- a/Sources/WalletConnectNotify/NotifyStorageIdntifiers.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -enum NotifyStorageIdntifiers { - static let notifySubscription = "com.walletconnect.notify.notifySubscription" - - static let notifyMessagesRecords = "com.walletconnect.sdk.notifyMessagesRecords" - static let coldStartStore = "com.walletconnect.sdk.coldStartStore" -} diff --git a/Sources/WalletConnectNotify/Types/DataStructures/NotifyMessage.swift b/Sources/WalletConnectNotify/Types/DataStructures/NotifyMessage.swift index 783721bbd..2cd90ad8c 100644 --- a/Sources/WalletConnectNotify/Types/DataStructures/NotifyMessage.swift +++ b/Sources/WalletConnectNotify/Types/DataStructures/NotifyMessage.swift @@ -5,9 +5,9 @@ public struct NotifyMessage: Codable, Equatable { public let body: String public let icon: String public let url: String - public let type: String? + public let type: String - public init(title: String, body: String, icon: String, url: String, type: String? = nil) { + public init(title: String, body: String, icon: String, url: String, type: String) { self.title = title self.body = body self.icon = icon From 0ad5de70465d40553ba54b8a5b6f64428b98ef6a Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Thu, 2 Nov 2023 00:48:27 +0800 Subject: [PATCH 5/8] Manual testing session --- Sources/Database/SQLiteQuery.swift | 2 +- Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift | 2 +- .../NotifySubscriptionsChangedRequestSubscriber.swift | 2 +- .../ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift | 2 +- .../NotifyWatchSubscriptionsResponseSubscriber.swift | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/Database/SQLiteQuery.swift b/Sources/Database/SQLiteQuery.swift index 9afa8c66a..a21927639 100644 --- a/Sources/Database/SQLiteQuery.swift +++ b/Sources/Database/SQLiteQuery.swift @@ -34,7 +34,7 @@ public struct SqliteQuery { } public static func select(table: String, where argument: String, equals value: String) -> String { - return "SELECT * FROM \(table) WHERE \(argument) = '\(value);" + return "SELECT * FROM \(table) WHERE \(argument) = '\(value)';" } public static func delete(table: String, where argument: String, equals value: String) -> String { diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift index 19134981e..62d1576fc 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift @@ -145,7 +145,7 @@ private extension NotifyDatabase { icon TEXT NOT NULL, url TEXT NOT NULL, type TEXT NOT NULL, - publishedAt TEXT NOT NULL, + publishedAt TEXT NOT NULL ); """) diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifySubscriptionsChanged/NotifySubscriptionsChangedRequestSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifySubscriptionsChanged/NotifySubscriptionsChangedRequestSubscriber.swift index 91f299e59..a7c9cdd95 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifySubscriptionsChanged/NotifySubscriptionsChangedRequestSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifySubscriptionsChanged/NotifySubscriptionsChangedRequestSubscriber.swift @@ -51,7 +51,7 @@ class NotifySubscriptionsChangedRequestSubscriber { // TODO: varify signature with notify server diddoc authentication key - let oldSubscriptions = try notifyStorage.getSubscriptions(account: account) + let oldSubscriptions = notifyStorage.getSubscriptions(account: account) let newSubscriptions = try await notifySubscriptionsBuilder.buildSubscriptions(jwtPayload.subscriptions) subscriptionChangedSubject.send(newSubscriptions) diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift index cf9a44f02..104c5d31a 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift @@ -35,7 +35,7 @@ class NotifyUpdateRequester: NotifyUpdateRequesting { func update(topic: String, scope: Set) async throws { logger.debug("NotifyUpdateRequester: updating subscription for topic: \(topic)") - guard let subscription = try notifyStorage.getSubscription(topic: topic) else { throw Errors.noSubscriptionForGivenTopic } + guard let subscription = notifyStorage.getSubscription(topic: topic) else { throw Errors.noSubscriptionForGivenTopic } let dappAuthenticationKey = try DIDKey(did: subscription.appAuthenticationKey) diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsResponseSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsResponseSubscriber.swift index 541c0279e..dccdcbdd7 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsResponseSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsResponseSubscriber.swift @@ -40,7 +40,7 @@ class NotifyWatchSubscriptionsResponseSubscriber { let account = watchSubscriptionPayloadRequest.subscriptionAccount // TODO: varify signature with notify server diddoc authentication key - let oldSubscriptions = try notifyStorage.getSubscriptions(account: account) + let oldSubscriptions = notifyStorage.getSubscriptions(account: account) let newSubscriptions = try await notifySubscriptionsBuilder.buildSubscriptions(responsePayload.subscriptions) try Task.checkCancellation() From 9cd1164bec73709421aa804f698475e21537848a Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Thu, 2 Nov 2023 01:27:34 +0800 Subject: [PATCH 6/8] Memory database for tests --- .../IntegrationTests/Push/NotifyTests.swift | 4 +- Sources/Database/DiskSqlite.swift | 51 +++++++++++++++++++ Sources/Database/MemorySqlite.swift | 49 ++++++++++++++++++ Sources/Database/Sqlite.swift | 45 ++-------------- Sources/Database/SqliteError.swift | 1 + .../Client/Wallet/NotifyClientFactory.swift | 19 +++++-- .../Client/Wallet/NotifyDatabase.swift | 25 ++------- .../Client/Wallet/NotifyStorage.swift | 6 +-- .../NotifyTests/Mocks/MockNotifyStoring.swift | 4 +- 9 files changed, 134 insertions(+), 70 deletions(-) create mode 100644 Sources/Database/DiskSqlite.swift create mode 100644 Sources/Database/MemorySqlite.swift 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 }) } } From d59c8c51192651a0c627c8cbdf191966a9b6c069 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Thu, 2 Nov 2023 01:30:23 +0800 Subject: [PATCH 7/8] Database library def removed --- Package.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Package.swift b/Package.swift index f43771ef7..0f7ce4e77 100644 --- a/Package.swift +++ b/Package.swift @@ -49,9 +49,6 @@ let package = Package( .library( name: "WalletConnectModal", targets: ["WalletConnectModal"]), - .library( - name: "Database", // TODO: Remove library - targets: ["Database"]), ], dependencies: [ From bd40f8e8dfee1bb5e608ca94e5a53f89e97ce2e8 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Thu, 2 Nov 2023 15:02:43 +0800 Subject: [PATCH 8/8] Sqlite journal_mode disabled --- Sources/Database/DiskSqlite.swift | 5 ----- Sources/Database/MemorySqlite.swift | 5 ----- 2 files changed, 10 deletions(-) diff --git a/Sources/Database/DiskSqlite.swift b/Sources/Database/DiskSqlite.swift index 516d75c63..19ffba05e 100644 --- a/Sources/Database/DiskSqlite.swift +++ b/Sources/Database/DiskSqlite.swift @@ -15,11 +15,6 @@ public final class DiskSqlite: Sqlite { 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] { diff --git a/Sources/Database/MemorySqlite.swift b/Sources/Database/MemorySqlite.swift index b0ea8ba3d..640098d04 100644 --- a/Sources/Database/MemorySqlite.swift +++ b/Sources/Database/MemorySqlite.swift @@ -9,11 +9,6 @@ public final class MemorySqlite: Sqlite { 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 {