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/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/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/Example/WalletApp/Other/Assets.xcassets/subscription_empty_icon.imageset/Contents.json b/Example/WalletApp/Other/Assets.xcassets/subscription_empty_icon.imageset/Contents.json new file mode 100644 index 000000000..2410c5c51 --- /dev/null +++ b/Example/WalletApp/Other/Assets.xcassets/subscription_empty_icon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "subscription_empty_icon.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/WalletApp/Other/Assets.xcassets/subscription_empty_icon.imageset/subscription_empty_icon.png b/Example/WalletApp/Other/Assets.xcassets/subscription_empty_icon.imageset/subscription_empty_icon.png new file mode 100644 index 000000000..70f324724 Binary files /dev/null and b/Example/WalletApp/Other/Assets.xcassets/subscription_empty_icon.imageset/subscription_empty_icon.png differ diff --git a/Example/WalletApp/Other/Assets.xcassets/subscriptions_empty_background.imageset/Contents.json b/Example/WalletApp/Other/Assets.xcassets/subscriptions_empty_background.imageset/Contents.json new file mode 100644 index 000000000..14f69feeb --- /dev/null +++ b/Example/WalletApp/Other/Assets.xcassets/subscriptions_empty_background.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "subscriptions_empty_background.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/WalletApp/Other/Assets.xcassets/subscriptions_empty_background.imageset/subscriptions_empty_background.png b/Example/WalletApp/Other/Assets.xcassets/subscriptions_empty_background.imageset/subscriptions_empty_background.png new file mode 100644 index 000000000..cfdcc06ae Binary files /dev/null and b/Example/WalletApp/Other/Assets.xcassets/subscriptions_empty_background.imageset/subscriptions_empty_background.png differ diff --git a/Example/WalletApp/Other/Assets.xcassets/subscriptions_empty_icon.imageset/Contents.json b/Example/WalletApp/Other/Assets.xcassets/subscriptions_empty_icon.imageset/Contents.json new file mode 100644 index 000000000..f775c3dcc --- /dev/null +++ b/Example/WalletApp/Other/Assets.xcassets/subscriptions_empty_icon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "subscriptions_empty_icon.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/WalletApp/Other/Assets.xcassets/subscriptions_empty_icon.imageset/subscriptions_empty_icon.svg b/Example/WalletApp/Other/Assets.xcassets/subscriptions_empty_icon.imageset/subscriptions_empty_icon.svg new file mode 100644 index 000000000..9f1eca535 --- /dev/null +++ b/Example/WalletApp/Other/Assets.xcassets/subscriptions_empty_icon.imageset/subscriptions_empty_icon.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift index 73151545c..343eac466 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift @@ -15,35 +15,51 @@ struct NotificationsView: View { List { Section { if selectedIndex == 0 { - notifications() + if presenter.subscriptionViewModels.isEmpty { + emptySubscriptionsView() + } else { + notifications() + } } else { discover() } } header: { - HStack { - SegmentedPicker(["Notifications", "Discover"], - selectedIndex: Binding( - get: { selectedIndex }, - set: { selectedIndex = $0 ?? 0 }), - content: { item, isSelected in - Text(item) - .font(.system(size: 16, weight: .medium)) - .foregroundColor(isSelected ? Color.primary : Color.secondary ) - .padding(.trailing, 32) - .padding(.vertical, 8) - }, selection: { - VStack(spacing: 0) { - Spacer() - Rectangle() - .fill(.Blue100) - .frame(height: 2) + VStack(spacing: 0) { + HStack { + SegmentedPicker(["Subscriptions", "Discover"], + selectedIndex: Binding( + get: { selectedIndex }, + set: { selectedIndex = $0 ?? 0 }), + content: { item, isSelected in + Text(item) + .font(.system(size: 16, weight: .medium)) + .foregroundColor(isSelected ? Color.primary : Color.secondary ) .padding(.trailing, 32) - } - }) + .padding(.vertical, 8) + }, selection: { + VStack(spacing: 0) { + Spacer() + Rectangle() + .fill(.Blue100) + .frame(height: 2) + .padding(.trailing, 32) + } + }) + .padding(.horizontal, 20) + .animation(.easeInOut(duration: 0.3)) + + Spacer() + } + + Rectangle() + .foregroundColor(.black.opacity(0.03)) + .frame(maxWidth: .infinity) + .frame(height: 1) } - .animation(.easeInOut(duration: 0.3)) .listRowBackground(Color.clear) } + .listRowInsets(EdgeInsets()) + .listRowSeparator(.hidden) } .listStyle(PlainListStyle()) } @@ -57,28 +73,37 @@ struct NotificationsView: View { discoverListRow(listing: listing) .listRowSeparator(.hidden) .listRowBackground(Color.clear) + .listRowInsets(EdgeInsets(top: 16, leading: 20, bottom: 0, trailing: 20)) } } private func emptySubscriptionsView() -> some View { - VStack(spacing: 10) { - Spacer() - - Image(systemName: "bell.badge.fill") + ZStack { + Image("subscriptions_empty_background") .resizable() - .frame(width: 32, height: 32) - .aspectRatio(contentMode: .fit) - .foregroundColor(.grey50) + .frame(maxWidth: .infinity) + .ignoresSafeArea() - Text("Notifications from connected apps will appear here. To enable notifications, visit the app in your browser and look for a \(Image(systemName: "bell.fill")) notifications toggle \(Image(systemName: "switch.2"))") - .foregroundColor(.grey50) - .font(.system(size: 15, weight: .regular, design: .rounded)) - .multilineTextAlignment(.center) - .lineSpacing(4) + VStack(spacing: 0) { + Image("subscriptions_empty_icon") - Spacer() + Text("Add your first app") + .foregroundColor(.Foreground100) + .font(.large700) + .padding(.bottom, 8.0) + + Text("Head over to “Discover” and\nsubscribe to one of our apps to start\nreceiving notifications") + .multilineTextAlignment(.center) + .foregroundColor(.Foreground200) + .font(.paragraph500) + .padding(.bottom, 16.0) + + Button("Discover apps") { + selectedIndex = 1 + } + .buttonStyle(W3MButtonStyle(size: .m, variant: .main)) + } } - .padding(20) } private func notifications() -> some View { diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift index 2a37713ef..1b579552d 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift @@ -1,4 +1,5 @@ import SwiftUI +import Web3ModalUI struct SubscriptionView: View { @@ -140,20 +141,21 @@ struct SubscriptionView: View { } func emptyStateView() -> some View { - VStack(spacing: 10) { - Image(systemName: "bell.badge.fill") - .resizable() - .frame(width: 32, height: 32) - .aspectRatio(contentMode: .fit) - .foregroundColor(.grey50) - - Text("Notifications from connected apps will appear here. To enable notifications, visit the app in your browser and look for a \(Image(systemName: "bell.fill")) notifications toggle \(Image(systemName: "switch.2"))") - .foregroundColor(.grey50) - .font(.system(size: 15, weight: .regular, design: .rounded)) - .multilineTextAlignment(.center) - .lineSpacing(4) + VStack(spacing: 0) { + Image("subscription_empty_icon") + .padding(.bottom, 24) + + Text("You’re ready to go") + .font(.large700) + .foregroundColor(.Foreground100) + .padding(.bottom, 8) + + Text("All new notifications will appear here.") + .font(.paragraph500) + .foregroundColor(.Foreground150) } - .padding(20) + .frame(maxWidth: .infinity) + .frame(height: 410) } } diff --git a/Package.swift b/Package.swift index b4c69b94b..0f7ce4e77 100644 --- a/Package.swift +++ b/Package.swift @@ -74,7 +74,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", @@ -130,6 +130,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/DiskSqlite.swift b/Sources/Database/DiskSqlite.swift new file mode 100644 index 000000000..19ffba05e --- /dev/null +++ b/Sources/Database/DiskSqlite.swift @@ -0,0 +1,46 @@ +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) + } + } + + 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..640098d04 --- /dev/null +++ b/Sources/Database/MemorySqlite.swift @@ -0,0 +1,44 @@ +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 + } + } + + 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/SQLiteQuery.swift b/Sources/Database/SQLiteQuery.swift new file mode 100644 index 000000000..a21927639 --- /dev/null +++ b/Sources/Database/SQLiteQuery.swift @@ -0,0 +1,50 @@ +import Foundation + +public struct SqliteQuery { + + public static func replace(table: String, rows: [SqliteRow]) throws -> String { + var values: [String] = [] + + 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 formattedValues = values + .map { "(\($0))" } + .joined(separator: ",\n") + + return """ + REPLACE INTO \(table) (\(formattedArguments)) VALUES + \(formattedValues); + """ + } + + public static func select(table: String) -> String { + 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)';" + } +} + +extension SqliteQuery { + + enum Errors: Error { + case rowsNotFound + } +} diff --git a/Sources/Database/Sqlite.swift b/Sources/Database/Sqlite.swift new file mode 100644 index 000000000..1770872af --- /dev/null +++ b/Sources/Database/Sqlite.swift @@ -0,0 +1,20 @@ +import Foundation +import SQLite3 + +public protocol Sqlite { + + /// Opening A New Database Connection + func openDatabase() throws + + /// Evaluate an SQL Statement + /// - Parameter sql: SQL query + /// - Returns: Table rows array + func query(sql: String) throws -> [Row] + + /// One-Step query execution + /// - Parameter sql: SQL query + func execute(sql: String) throws + + /// Closing A Database Connection + func closeConnection() +} diff --git a/Sources/Database/SqliteError.swift b/Sources/Database/SqliteError.swift new file mode 100644 index 000000000..9915bca06 --- /dev/null +++ b/Sources/Database/SqliteError.swift @@ -0,0 +1,11 @@ +import Foundation + +public enum SQLiteError: Error { + case openDatabase(path: String) + case openDatabaseMemory + 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 new file mode 100644 index 000000000..f1a683083 --- /dev/null +++ b/Sources/Database/SqliteRow.swift @@ -0,0 +1,12 @@ +import Foundation + +public protocol SqliteRow { + + /// 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 new file mode 100644 index 000000000..bd9bde816 --- /dev/null +++ b/Sources/Database/SqliteRowDecoder.swift @@ -0,0 +1,51 @@ +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 + } + + /// 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/WalletConnectModal/Extensions/Bundle.swift b/Sources/WalletConnectModal/Extensions/Bundle.swift new file mode 100644 index 000000000..a68d32564 --- /dev/null +++ b/Sources/WalletConnectModal/Extensions/Bundle.swift @@ -0,0 +1,7 @@ +import Foundation + +#if CocoaPods +extension Bundle { + static var module: Bundle { Bundle.init(for: WalletConnectModal.self) } +} +#endif diff --git a/Sources/WalletConnectModal/Extensions/Image+Backport.swift b/Sources/WalletConnectModal/Extensions/Image+Backport.swift new file mode 100644 index 000000000..bab1f5e17 --- /dev/null +++ b/Sources/WalletConnectModal/Extensions/Image+Backport.swift @@ -0,0 +1,12 @@ +import SwiftUI + +extension Image { + + init(sfSymbolName: String) { + if #available(macOS 11, iOS 13, *) { + self.init(systemName: sfSymbolName) + } else { + self.init("", bundle: nil) + } + } +} diff --git a/Sources/WalletConnectModal/Extensions/View+Backport.swift b/Sources/WalletConnectModal/Extensions/View+Backport.swift index c8025f061..b93de0e16 100644 --- a/Sources/WalletConnectModal/Extensions/View+Backport.swift +++ b/Sources/WalletConnectModal/Extensions/View+Backport.swift @@ -2,6 +2,9 @@ import Combine import SwiftUI extension View { + + #if os(iOS) + /// A backwards compatible wrapper for iOS 14 `onChange` @ViewBuilder func onChangeBackported(of value: T, perform: @escaping (T) -> Void) -> some View { @@ -13,4 +16,15 @@ extension View { } } } + + #elseif os(macOS) + + @ViewBuilder + func onChangeBackported(of value: T, perform: @escaping (T) -> Void) -> some View { + self.onReceive(Just(value)) { value in + perform(value) + } + } + + #endif } diff --git a/Sources/WalletConnectModal/Modal/ModalContainerView.swift b/Sources/WalletConnectModal/Modal/ModalContainerView.swift index 4bdbefc8a..ab24e444b 100644 --- a/Sources/WalletConnectModal/Modal/ModalContainerView.swift +++ b/Sources/WalletConnectModal/Modal/ModalContainerView.swift @@ -37,11 +37,15 @@ struct ModalContainerView: View { ) .edgesIgnoringSafeArea(.all) .transform { - if #available(iOS 14.0, *) { - $0.ignoresSafeArea(.keyboard, edges: .bottom) - } else { + #if os(iOS) + if #available(iOS 14.0, *) { + $0.ignoresSafeArea(.keyboard, edges: .bottom) + } else { + $0 + } + #else $0 - } + #endif } .onChangeBackported(of: showModal, perform: { newValue in if newValue == false { diff --git a/Sources/WalletConnectModal/Modal/ModalSheet.swift b/Sources/WalletConnectModal/Modal/ModalSheet.swift index c51e69d2b..b09226db0 100644 --- a/Sources/WalletConnectModal/Modal/ModalSheet.swift +++ b/Sources/WalletConnectModal/Modal/ModalSheet.swift @@ -81,7 +81,7 @@ public struct ModalSheet: View { VStack { if viewModel.destination.hasSearch { HStack { - Image(systemName: "magnifyingglass") + Image(sfSymbolName: "magnifyingglass") TextField("Search", text: $viewModel.searchTerm, onEditingChanged: { editing in self.searchEditing = editing }) @@ -186,7 +186,7 @@ extension ModalSheet { viewModel.onBackButton() } } label: { - Image(systemName: "chevron.backward") + Image(sfSymbolName: "chevron.backward") .padding(20) } } diff --git a/Sources/WalletConnectModal/Modal/Screens/GetAWalletView.swift b/Sources/WalletConnectModal/Modal/Screens/GetAWalletView.swift index d9f8e1f30..b5f2c7438 100644 --- a/Sources/WalletConnectModal/Modal/Screens/GetAWalletView.swift +++ b/Sources/WalletConnectModal/Modal/Screens/GetAWalletView.swift @@ -22,7 +22,7 @@ struct GetAWalletView: View { Spacer() - Image(systemName: "chevron.right") + Image(sfSymbolName: "chevron.right") .font(.system(.footnote).weight(.semibold)) } } diff --git a/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetail.swift b/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetail.swift index 3bae82d17..2fb5f1bff 100644 --- a/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetail.swift +++ b/Sources/WalletConnectModal/Modal/Screens/WalletDetail/WalletDetail.swift @@ -18,9 +18,9 @@ struct WalletDetail: View { HStack { switch item { case .native: - Image(systemName: "iphone") + Image(sfSymbolName: "iphone") case .browser: - Image(systemName: "safari") + Image(sfSymbolName: "safari") } Text(item.rawValue.capitalized) } @@ -181,7 +181,7 @@ struct WalletDetail: View { .foregroundColor(.foreground2) .font(.system(size: 14).weight(.semibold)) - Image(systemName: "chevron.right") + Image(sfSymbolName: "chevron.right") .foregroundColor(.foreground2) } } diff --git a/Sources/WalletConnectModal/Networking/Explorer/ExplorerAPI.swift b/Sources/WalletConnectModal/Networking/Explorer/ExplorerAPI.swift index 806ae02c7..f52a3db67 100644 --- a/Sources/WalletConnectModal/Networking/Explorer/ExplorerAPI.swift +++ b/Sources/WalletConnectModal/Networking/Explorer/ExplorerAPI.swift @@ -1,5 +1,4 @@ import Foundation -import HTTPClient enum ExplorerAPI: HTTPService { case getListings( diff --git a/Sources/WalletConnectModal/UI/Common/Toast.swift b/Sources/WalletConnectModal/UI/Common/Toast.swift index f8e276a25..e637cee46 100644 --- a/Sources/WalletConnectModal/UI/Common/Toast.swift +++ b/Sources/WalletConnectModal/UI/Common/Toast.swift @@ -40,7 +40,7 @@ struct ToastView: View { var body: some View { HStack(alignment: .center, spacing: 12) { - Image(systemName: style.iconFileName) + Image(sfSymbolName: style.iconFileName) .foregroundColor(style.themeColor) Text(message) .font(Font.caption) @@ -51,7 +51,7 @@ struct ToastView: View { Button { onCancelTapped() } label: { - Image(systemName: "xmark") + Image(sfSymbolName: "xmark") .foregroundColor(style.themeColor) } } diff --git a/Sources/WalletConnectModal/UI/Common/Web3ModalPicker.swift b/Sources/WalletConnectModal/UI/Common/Web3ModalPicker.swift index 0822b7fc9..31ba098ad 100644 --- a/Sources/WalletConnectModal/UI/Common/Web3ModalPicker.swift +++ b/Sources/WalletConnectModal/UI/Common/Web3ModalPicker.swift @@ -94,7 +94,7 @@ struct PreviewWeb3ModalPicker: View { ) { item in HStack { - Image(systemName: "iphone") + Image(sfSymbolName: "iphone") Text(item.rawValue.capitalized) } .font(.system(size: 14).weight(.semibold)) 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..bba5af964 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() } @@ -114,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 9d0e41f4a..807927a7f 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift @@ -8,10 +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, + sqlite: sqlite, logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychainStorage, @@ -28,6 +31,7 @@ public struct NotifyClientFactory { static func create( projectId: String, keyserverURL: URL, + sqlite: Sqlite, logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, @@ -40,10 +44,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 notifyStorage = NotifyStorage(subscriptionStore: subscriptionStore, messagesStore: messagesStore, accountProvider: notifyAccountProvider) + 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) let webDidResolver = NotifyWebDidResolver() @@ -92,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 new file mode 100644 index 000000000..9e6110e2b --- /dev/null +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyDatabase.swift @@ -0,0 +1,156 @@ +import Foundation +import Database +import Combine + +final class NotifyDatabase { + + enum Table { + static let subscriptions = "NotifySubscription" + static let messages = "NotifyMessage" + } + + private let sqlite: Sqlite + private let logger: ConsoleLogging + + var onSubscriptionsUpdate: (() throws -> Void)? + var onMessagesUpdate: (() throws -> Void)? + + init(sqlite: Sqlite, logger: ConsoleLogging) { + self.sqlite = sqlite + self.logger = logger + + prepareDatabase() + } + + // MARK: - NotifySubscriptions + + func save(subscription: NotifySubscription) throws { + try save(subscriptions: [subscription]) + } + + 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) -> NotifySubscription? { + let sql = SqliteQuery.select(table: Table.subscriptions, where: "topic", equals: topic) + let subscriptions: [NotifySubscription]? = try? query(sql: sql) + return subscriptions?.first + } + + func getAllSubscriptions() -> [NotifySubscription] { + let sql = SqliteQuery.select(table: Table.subscriptions) + let subscriptions: [NotifySubscription]? = try? query(sql: sql) + return subscriptions ?? [] + } + + 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 { + 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?() + } + + // 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 { + + func prepareDatabase() { + do { + defer { sqlite.closeConnection() } + try sqlite.openDatabase() + + try sqlite.execute(sql: """ + CREATE TABLE IF NOT EXISTS \(Table.subscriptions) ( + 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 + ); + """) + + 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") + } catch { + logger.error("SQlite database creation error: \(error.localizedDescription)") + } + } + + func execute(sql: String) throws { + try sqlite.openDatabase() + defer { sqlite.closeConnection() } + + try sqlite.execute(sql: sql) + } + + func query(sql: String) throws -> [T] { + try sqlite.openDatabase() + defer { sqlite.closeConnection() } + + return try sqlite.query(sql: sql) + } +} 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 ab873dcf1..832e92dc3 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift @@ -5,17 +5,16 @@ 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 setSubscription(_ subscription: NotifySubscription) throws + func deleteSubscription(topic: String) throws + func clearDatabase(account: Account) 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,9 +40,8 @@ final class NotifyStorage: NotifyStoring { return subscriptionsSubject.eraseToAnyPublisher() } - init(subscriptionStore: KeyedDatabase, messagesStore: KeyedDatabase, accountProvider: NotifyAccountProvider) { - self.subscriptionStore = subscriptionStore - self.messagesStore = messagesStore + init(database: NotifyDatabase, accountProvider: NotifyAccountProvider) { + self.database = database self.accountProvider = accountProvider setupSubscriptions() @@ -52,45 +50,42 @@ final class NotifyStorage: NotifyStoring { // MARK: Subscriptions func getAllSubscriptions() -> [NotifySubscription] { - return subscriptionStore.getAll() + return database.getAllSubscriptions() } func getSubscriptions(account: Account) -> [NotifySubscription] { - return subscriptionStore.getAll(for: account.absoluteString) + return database.getSubscriptions(account: account) } func getSubscription(topic: String) -> NotifySubscription? { - return subscriptionStore.getAll().first(where: { $0.topic == topic }) + return 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) { + func clearDatabase(account: Account) throws { for subscription in getSubscriptions(account: account) { - deleteMessages(topic: subscription.topic) + try database.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) } @@ -103,21 +98,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) } } @@ -128,12 +121,12 @@ private extension NotifyStorage { } func setupSubscriptions() { - messagesStore.onUpdate = { [unowned self] in - messagesSubject.send(messagesStore.getAll()) + database.onMessagesUpdate = { [unowned self] in + messagesSubject.send(database.getAllMessages()) } - subscriptionStore.onUpdate = { [unowned self] in - guard let account = try? accountProvider.getCurrentAccount() else { return } + database.onSubscriptionsUpdate = { [unowned self] in + let account = try accountProvider.getCurrentAccount() 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 a94e86420..b9c86009c 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/DeleteNotifySubscriptionRequester.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/DeleteNotifySubscriptionRequester.swift @@ -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_notifySubscriptionsChanged/NotifySubscriptionsChangedRequestSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifySubscriptionsChanged/NotifySubscriptionsChangedRequestSubscriber.swift index 18c8bbc31..a7c9cdd95 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifySubscriptionsChanged/NotifySubscriptionsChangedRequestSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifySubscriptionsChanged/NotifySubscriptionsChangedRequestSubscriber.swift @@ -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_notifyWatchSubscriptions/NotifyWatchSubscriptionsResponseSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsResponseSubscriber.swift index 55323843e..dccdcbdd7 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsResponseSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsResponseSubscriber.swift @@ -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/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/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/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 diff --git a/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift b/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift index cb4982555..9a5ac3609 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,52 @@ 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) + self.appAuthenticationKey = try decoder.decodeString(at: 7) + } + + 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") + 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 { diff --git a/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json index ae47f4ee5..f828972fc 100644 --- a/Sources/WalletConnectRelay/PackageConfig.json +++ b/Sources/WalletConnectRelay/PackageConfig.json @@ -1 +1 @@ -{"version": "1.9.5"} +{"version": "1.9.6"} diff --git a/Sources/WalletConnectRouter/Router/Router.swift b/Sources/WalletConnectRouter/Router/Router.swift index b97566675..75a227b31 100644 --- a/Sources/WalletConnectRouter/Router/Router.swift +++ b/Sources/WalletConnectRouter/Router/Router.swift @@ -1,7 +1,5 @@ import UIKit -@_exported import WalletConnectRouter - public struct WalletConnectRouter { public static func goBack(uri: String) { if #available(iOS 17, *) { diff --git a/Sources/WalletConnectRouter/Router/RouterImports.swift b/Sources/WalletConnectRouter/Router/RouterImports.swift new file mode 100644 index 000000000..e2e65b0f9 --- /dev/null +++ b/Sources/WalletConnectRouter/Router/RouterImports.swift @@ -0,0 +1,3 @@ +#if !CocoaPods +@_exported import WalletConnectRouter +#endif 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 }) } } diff --git a/WalletConnectSwiftV2.podspec b/WalletConnectSwiftV2.podspec index cc6ab4f3d..29cf98ffe 100644 --- a/WalletConnectSwiftV2.podspec +++ b/WalletConnectSwiftV2.podspec @@ -189,4 +189,10 @@ Pod::Spec.new do |spec| spec.subspec 'HTTPClient' do |ss| ss.source_files = 'Sources/HTTPClient/**/*.{h,m,swift}' end + + spec.subspec 'WalletConnectModal' do |ss| + ss.source_files = 'Sources/WalletConnectModal/**/*.{h,m,swift}' + ss.dependency 'WalletConnectSwiftV2/WalletConnectSign' + ss.dependency 'DSF_QRCode', '~> 16.1.1' + end end