Skip to content

Commit

Permalink
Merge pull request #1294 from WalletConnect/feature/icon-from-notific…
Browse files Browse the repository at this point in the history
…ation-types

[Sample] Icons from notification types
  • Loading branch information
flypaper0 authored Jan 30, 2024
2 parents c54284a + 6aa35d5 commit c2d4c55
Show file tree
Hide file tree
Showing 11 changed files with 132 additions and 81 deletions.
118 changes: 61 additions & 57 deletions Example/PNDecryptionService/NotificationService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,11 @@ class NotificationService: UNNotificationServiceExtension {
private func handleNotifyNotification(content: UNNotificationContent, topic: String, ciphertext: String) -> UNMutableNotificationContent {
do {
let service = NotifyDecryptionService(groupIdentifier: "group.com.walletconnect.sdk")
let (pushMessage, account) = try service.decryptMessage(topic: topic, ciphertext: ciphertext)
let (pushMessage, subscription, account) = try service.decryptMessage(topic: topic, ciphertext: ciphertext)

log("message decrypted", account: account, topic: topic, message: pushMessage)

let updatedContent = handle(content: content, pushMessage: pushMessage, topic: topic)
let updatedContent = handle(content: content, pushMessage: pushMessage, subscription: subscription, topic: topic)

let mutableContent = updatedContent.mutableCopy() as! UNMutableNotificationContent
mutableContent.title = pushMessage.title
Expand Down Expand Up @@ -114,62 +114,66 @@ class NotificationService: UNNotificationServiceExtension {

private extension NotificationService {

func handle(content: UNNotificationContent, pushMessage: NotifyMessage, topic: String) -> UNNotificationContent {
do {
let iconUrl = try pushMessage.icon.asURL()

let senderThumbnailImageData = try Data(contentsOf: iconUrl)
let senderThumbnailImageFileUrl = try downloadAttachment(data: senderThumbnailImageData, fileName: iconUrl.lastPathComponent)
let senderThumbnailImageFileData = try Data(contentsOf: senderThumbnailImageFileUrl)
let senderAvatar = INImage(imageData: senderThumbnailImageFileData)

var personNameComponents = PersonNameComponents()
personNameComponents.nickname = pushMessage.title

let senderPerson = INPerson(
personHandle: INPersonHandle(value: topic, type: .unknown),
nameComponents: personNameComponents,
displayName: pushMessage.title,
image: senderAvatar,
contactIdentifier: nil,
customIdentifier: topic,
isMe: false,
suggestionType: .none
)

let selfPerson = INPerson(
personHandle: INPersonHandle(value: "0", type: .unknown),
nameComponents: nil,
displayName: nil,
image: nil,
contactIdentifier: nil,
customIdentifier: nil,
isMe: true,
suggestionType: .none
)

let incomingMessagingIntent = INSendMessageIntent(
recipients: [selfPerson],
outgoingMessageType: .outgoingMessageText,
content: pushMessage.body,
speakableGroupName: nil,
conversationIdentifier: pushMessage.type,
serviceName: nil,
sender: senderPerson,
attachments: []
)

incomingMessagingIntent.setImage(senderAvatar, forParameterNamed: \.sender)

let interaction = INInteraction(intent: incomingMessagingIntent, response: nil)
interaction.direction = .incoming
interaction.donate(completion: nil)

return try content.updating(from: incomingMessagingIntent)
}
catch {
return content
func handle(content: UNNotificationContent, pushMessage: NotifyMessage, subscription: NotifySubscription, topic: String) -> UNNotificationContent {

var senderAvatar: INImage?

if let icon = subscription.messageIcons(ofType: pushMessage.type).md {
do {
let iconUrl = try icon.asURL()
let senderThumbnailImageData = try Data(contentsOf: iconUrl)
let senderThumbnailImageFileUrl = try downloadAttachment(data: senderThumbnailImageData, fileName: iconUrl.lastPathComponent)
let senderThumbnailImageFileData = try Data(contentsOf: senderThumbnailImageFileUrl)
senderAvatar = INImage(imageData: senderThumbnailImageFileData)
} catch {
log("Fetch icon error: \(error)", account: subscription.account, topic: topic, message: pushMessage)
}
}

var personNameComponents = PersonNameComponents()
personNameComponents.nickname = pushMessage.title

let senderPerson = INPerson(
personHandle: INPersonHandle(value: topic, type: .unknown),
nameComponents: personNameComponents,
displayName: pushMessage.title,
image: senderAvatar,
contactIdentifier: nil,
customIdentifier: topic,
isMe: false,
suggestionType: .none
)

let selfPerson = INPerson(
personHandle: INPersonHandle(value: "0", type: .unknown),
nameComponents: nil,
displayName: nil,
image: nil,
contactIdentifier: nil,
customIdentifier: nil,
isMe: true,
suggestionType: .none
)

let incomingMessagingIntent = INSendMessageIntent(
recipients: [selfPerson],
outgoingMessageType: .outgoingMessageText,
content: pushMessage.body,
speakableGroupName: nil,
conversationIdentifier: pushMessage.type,
serviceName: nil,
sender: senderPerson,
attachments: []
)

incomingMessagingIntent.setImage(senderAvatar, forParameterNamed: \.sender)

let interaction = INInteraction(intent: incomingMessagingIntent, response: nil)
interaction.direction = .incoming
interaction.donate(completion: nil)

let updated = try? content.updating(from: incomingMessagingIntent)
return updated ?? content
}

func downloadAttachment(data: Data, fileName: String) throws -> URL {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ struct SubscriptionsViewModel: Identifiable {
}

var hasMessage: Bool {
return messagesCount != 0
/* return messagesCount != 0 Badge disabled */
return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ struct NotificationsView: View {
.listRowSeparator(.hidden)
}
.listStyle(PlainListStyle())
.safeAreaInset(edge: .bottom) {
Spacer().frame(height: 16)
}
}
.task {
try? await presenter.fetch()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,8 @@ struct NotifyMessageViewModel: Identifiable {
var publishedAt: String {
return pushMessageRecord.publishedAt.formatted(.relative(presentation: .named, unitsStyle: .wide))
}

var type: String {
return pushMessageRecord.message.type
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ final class SubscriptionPresenter: ObservableObject {
}
}

func messageIconUrl(message: NotifyMessageViewModel) -> URL? {
let icons = subscription.messageIcons(ofType: message.type)
return try? icons.md?.asURL()
}

func unsubscribe() {
interactor.deleteSubscription(subscription)
router.dismiss()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ struct SubscriptionView: View {
private func notificationView(pushMessage: NotifyMessageViewModel) -> some View {
VStack(alignment: .center) {
HStack(spacing: 12) {
CacheAsyncImage(url: URL(string: pushMessage.imageUrl) ?? presenter.subscriptionViewModel.imageUrl) { phase in
CacheAsyncImage(url: presenter.messageIconUrl(message: pushMessage)) { phase in
if let image = phase.image {
image
.resizable()
.frame(width: 48, height: 48)
.background(Color.black)
.background(Color.black.opacity(0.05))
.cornerRadius(10, corners: .allCorners)
} else {
Color.black
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ public struct NotifyClientFactory {
let keyserverURL = URL(string: "https://keys.walletconnect.com")!
let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk", accessGroup: groupIdentifier)
let groupKeychainService = GroupKeychainStorage(serviceIdentifier: groupIdentifier)
let databasePath = databasePath(appGroup: groupIdentifier, database: "notify_v\(version).db")
let sqlite = DiskSqlite(path: databasePath)
let sqlite = NotifySqliteFactory.create(appGroup: groupIdentifier)

return NotifyClientFactory.create(
projectId: projectId,
Expand Down Expand Up @@ -97,19 +96,4 @@ 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
}

static var version: String {
return "1"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,36 @@ import Foundation
public class NotifyDecryptionService {
enum Errors: Error {
case malformedNotifyMessage
case subsctiptionNotFound
}
private let serializer: Serializing
private let database: NotifyDatabase
private static let notifyTags: [UInt] = [4002]

init(serializer: Serializing) {
init(serializer: Serializing, database: NotifyDatabase) {
self.serializer = serializer
self.database = database
}

public init(groupIdentifier: String) {
let keychainStorage = GroupKeychainStorage(serviceIdentifier: groupIdentifier)
let kms = KeyManagementService(keychain: keychainStorage)
self.serializer = Serializer(kms: kms, logger: ConsoleLogger(prefix: "🔐", loggingLevel: .off))
let logger = ConsoleLogger(prefix: "🔐", loggingLevel: .off)
let sqlite = NotifySqliteFactory.create(appGroup: groupIdentifier)
self.serializer = Serializer(kms: kms, logger: logger)
self.database = NotifyDatabase(sqlite: sqlite, logger: logger)
}

public static func canHandle(tag: UInt) -> Bool {
return notifyTags.contains(tag)
}

public func decryptMessage(topic: String, ciphertext: String) throws -> (NotifyMessage, Account) {
public func decryptMessage(topic: String, ciphertext: String) throws -> (NotifyMessage, NotifySubscription, Account) {
let (rpcRequest, _, _): (RPCRequest, String?, Data) = try serializer.deserialize(topic: topic, encodedEnvelope: ciphertext)
guard let params = rpcRequest.params else { throw Errors.malformedNotifyMessage }
let wrapper = try params.get(NotifyMessagePayload.Wrapper.self)
let (messagePayload, _) = try NotifyMessagePayload.decodeAndVerify(from: wrapper)
return (messagePayload.message, messagePayload.account)
guard let subscription = database.getSubscription(topic: topic) else { throw Errors.subsctiptionNotFound }
return (messagePayload.message, subscription, messagePayload.account)
}
}
14 changes: 14 additions & 0 deletions Sources/WalletConnectNotify/Client/Wallet/NotifyImageUrls.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
import Foundation

public struct NotifyImageUrls: Codable, Equatable {

public let sm: String?
public let md: String?
public let lg: String?

public init(sm: String? = nil, md: String? = nil, lg: String? = nil) {
self.sm = sm
self.md = md
self.lg = lg
}

public init?(icons: [String]) {
guard icons.count == 3 else { return nil }
self.sm = icons[0]
self.md = icons[1]
self.lg = icons[2]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Foundation

struct NotifySqliteFactory {

static func create(appGroup: String) -> Sqlite {
let databasePath = databasePath(appGroup: appGroup, database: "notify_v\(version).db")
let sqlite = DiskSqlite(path: databasePath)
return sqlite
}

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
}

static var version: String {
return "1"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ public struct NotifySubscription: Codable, Equatable, SqliteRow {
return "\(account.absoluteString)-\(metadata.url)"
}

public func messageIcons(ofType type: String) -> NotifyImageUrls {
return scope[type]?.imageUrls ?? NotifyImageUrls(icons: metadata.icons) ?? NotifyImageUrls()
}

public init(decoder: SqliteRowDecoder) throws {
self.topic = try decoder.decodeString(at: 0)
self.account = try Account(decoder.decodeString(at: 1))!
Expand Down

0 comments on commit c2d4c55

Please sign in to comment.