From 7855091138af73dff0f72889fdaf5af94cd3297b Mon Sep 17 00:00:00 2001 From: Hamish Peebles Date: Mon, 9 Oct 2023 09:57:48 +0100 Subject: [PATCH] Display notification when someone tips one of your messages (#4530) --- .../services/notifications/candid/idl.d.ts | 6 + .../src/services/notifications/mappers.ts | 106 ++++++++++++++++-- frontend/openchat-push/src/push_sw.ts | 65 +++++++++-- .../src/domain/notifications/index.ts | 55 ++++++++- 4 files changed, 206 insertions(+), 26 deletions(-) diff --git a/frontend/openchat-agent/src/services/notifications/candid/idl.d.ts b/frontend/openchat-agent/src/services/notifications/candid/idl.d.ts index d3f1297ecb..5320685eb3 100644 --- a/frontend/openchat-agent/src/services/notifications/candid/idl.d.ts +++ b/frontend/openchat-agent/src/services/notifications/candid/idl.d.ts @@ -9,6 +9,9 @@ import { ChannelReactionAddedNotification, DirectReactionAddedNotification, GroupReactionAddedNotification, + ChannelMessageTippedNotification, + DirectMessageTippedNotification, + GroupMessageTippedNotification, NotificationCryptoTransferDetails, SubscriptionExistsResponse, } from "./types"; @@ -22,6 +25,9 @@ export { ChannelReactionAddedNotification as ApiChannelReactionAddedNotification, DirectReactionAddedNotification as ApiDirectReactionAddedNotification, GroupReactionAddedNotification as ApiGroupReactionAddedNotification, + ChannelMessageTippedNotification as ApiChannelMessageTippedNotification, + DirectMessageTippedNotification as ApiDirectMessageTippedNotification, + GroupMessageTippedNotification as ApiGroupMessageTippedNotification, NotificationCryptoTransferDetails as ApiNotificationCryptoTransferDetails, SubscriptionExistsResponse as ApiSubscriptionExistsResponse, }; diff --git a/frontend/openchat-agent/src/services/notifications/mappers.ts b/frontend/openchat-agent/src/services/notifications/mappers.ts index 2de526bdec..7826b5de44 100644 --- a/frontend/openchat-agent/src/services/notifications/mappers.ts +++ b/frontend/openchat-agent/src/services/notifications/mappers.ts @@ -9,6 +9,9 @@ import type { ChannelReaction, GroupReaction, DirectReaction, + ChannelMessageTipped, + GroupMessageTipped, + DirectMessageTipped, CryptoTransferDetails, } from "openchat-shared"; import { UnsupportedValueError } from "openchat-shared"; @@ -27,6 +30,9 @@ import type { ApiChannelReactionAddedNotification, ApiGroupReactionAddedNotification, ApiDirectReactionAddedNotification, + ApiChannelMessageTippedNotification, + ApiGroupMessageTippedNotification, + ApiDirectMessageTippedNotification, ApiNotificationCryptoTransferDetails, } from "./candid/idl"; import type { ApiToggleMuteChannelNotificationsResponse } from "../community/candid/idl"; @@ -35,7 +41,7 @@ export function muteNotificationsResponse( candid: | ApiMuteNotificationsResponse | ApiUnmuteNotificationsResponse - | ApiToggleMuteChannelNotificationsResponse + | ApiToggleMuteChannelNotificationsResponse, ): ToggleMuteNotificationResponse { if ("Success" in candid) { return "success"; @@ -46,7 +52,7 @@ export function muteNotificationsResponse( } export function subscriptionExistsResponse( - candid: ApiSubscriptionExistsResponse + candid: ApiSubscriptionExistsResponse, ): SubscriptionExistsResponse { if ("Yes" in candid) { return true; @@ -56,7 +62,7 @@ export function subscriptionExistsResponse( } throw new UnsupportedValueError( `Unexpected ApiSubscriptionExistsResponse type received`, - candid + candid, ); } @@ -82,16 +88,29 @@ export function notification(candid: ApiNotification, timestamp: bigint): Notifi if ("DirectReactionAdded" in candid) { return directReactionNotification(candid.DirectReactionAdded, timestamp); } + if ("ChannelMessageTipped" in candid) { + return channelMessageTipped(candid.ChannelMessageTipped, timestamp); + } + if ("GroupMessageTipped" in candid) { + return groupMessageTipped(candid.GroupMessageTipped, timestamp); + } + if ("DirectMessageTipped" in candid) { + return directMessageTipped(candid.DirectMessageTipped, timestamp); + } throw new Error(`Unexpected ApiNotification type received, ${candid}`); } export function addedToChannelNotification( candid: ApiAddedToChannelNotification, - timestamp : bigint, + timestamp: bigint, ): AddedToChannelNotification { return { kind: "added_to_channel_notification", - chatId: { kind: "channel", communityId: candid.community_id.toString(), channelId: candid.channel_id.toString() }, + chatId: { + kind: "channel", + communityId: candid.community_id.toString(), + channelId: candid.channel_id.toString(), + }, communityName: candid.community_name, channelName: candid.channel_name, addedBy: candid.added_by.toString(), @@ -105,7 +124,7 @@ export function addedToChannelNotification( export function channelNotification( candid: ApiChannelMessageNotification, - timestamp : bigint, + timestamp: bigint, ): ChannelNotification { return { kind: "channel_notification", @@ -115,7 +134,11 @@ export function channelNotification( eventIndex: candid.event_index, senderName: candid.sender_name, senderDisplayName: optional(candid.sender_display_name, identity), - chatId: { kind: "channel", communityId: candid.community_id.toString(), channelId: candid.channel_id.toString() }, + chatId: { + kind: "channel", + communityId: candid.community_id.toString(), + channelId: candid.channel_id.toString(), + }, communityName: candid.community_name, channelName: candid.channel_name, messageType: candid.message_type, @@ -177,7 +200,11 @@ function channelReactionNotification( ): ChannelReaction { return { kind: "channel_reaction", - chatId: { kind: "channel", communityId: candid.community_id.toString(), channelId: candid.channel_id.toString() }, + chatId: { + kind: "channel", + communityId: candid.community_id.toString(), + channelId: candid.channel_id.toString(), + }, communityName: candid.community_name, channelName: candid.channel_name, threadRootMessageIndex: optional(candid.thread_root_message_index, identity), @@ -230,6 +257,69 @@ function directReactionNotification( }; } +function channelMessageTipped( + candid: ApiChannelMessageTippedNotification, + timestamp: bigint, +): ChannelMessageTipped { + return { + kind: "channel_message_tipped", + chatId: { + kind: "channel", + communityId: candid.community_id.toString(), + channelId: candid.channel_id.toString(), + }, + communityName: candid.community_name, + channelName: candid.channel_name, + threadRootMessageIndex: optional(candid.thread_root_message_index, identity), + messageIndex: candid.message_index, + messageEventIndex: candid.message_event_index, + tippedBy: candid.tipped_by.toString(), + tippedByName: candid.tipped_by_name, + tippedByDisplayName: optional(candid.tipped_by_display_name, identity), + tip: candid.tip, + communityAvatarId: optional(candid.community_avatar_id, identity), + channelAvatarId: optional(candid.channel_avatar_id, identity), + timestamp, + }; +} + +function groupMessageTipped( + candid: ApiGroupMessageTippedNotification, + timestamp: bigint, +): GroupMessageTipped { + return { + kind: "group_message_tipped", + chatId: { kind: "group_chat", groupId: candid.chat_id.toString() }, + threadRootMessageIndex: optional(candid.thread_root_message_index, identity), + messageIndex: candid.message_index, + messageEventIndex: candid.message_event_index, + groupName: candid.group_name, + tippedBy: candid.tipped_by.toString(), + tippedByName: candid.tipped_by_name, + tippedByDisplayName: optional(candid.tipped_by_display_name, identity), + tip: candid.tip, + groupAvatarId: optional(candid.group_avatar_id, identity), + timestamp, + }; +} + +function directMessageTipped( + candid: ApiDirectMessageTippedNotification, + timestamp: bigint, +): DirectMessageTipped { + return { + kind: "direct_message_tipped", + them: { kind: "direct_chat", userId: candid.them.toString() }, + messageIndex: candid.message_index, + messageEventIndex: candid.message_event_index, + username: candid.username, + displayName: optional(candid.display_name, identity), + tip: candid.tip, + userAvatarId: optional(candid.user_avatar_id, identity), + timestamp, + }; +} + function cryptoTransfer(candid: ApiNotificationCryptoTransferDetails): CryptoTransferDetails { return { recipient: candid.recipient.toString(), diff --git a/frontend/openchat-push/src/push_sw.ts b/frontend/openchat-push/src/push_sw.ts index 633d9743df..da9b164ac4 100644 --- a/frontend/openchat-push/src/push_sw.ts +++ b/frontend/openchat-push/src/push_sw.ts @@ -1,5 +1,9 @@ import { IDL } from "@dfinity/candid"; -import { type ApiNotification, NotificationIdl, notification as toNotification } from "openchat-agent"; +import { + type ApiNotification, + NotificationIdl, + notification as toNotification, +} from "openchat-agent"; import type { Notification, DirectChatIdentifier, @@ -133,11 +137,7 @@ async function showNotification(n: Notification, id: string): Promise { userId: n.sender.userId, }; title = n.senderDisplayName ?? n.senderName; - body = messageText( - n.messageText, - n.messageType, - n.cryptoTransfer, - ); + body = messageText(n.messageText, n.messageType, n.cryptoTransfer); if (n.senderAvatarId !== undefined) { icon = avatarUrl(n.sender.userId, n.senderAvatarId); } else if (n.messageType === "File") { @@ -202,11 +202,7 @@ async function showNotification(n: Notification, id: string): Promise { if (n.userAvatarId !== undefined) { icon = avatarUrl(n.them.userId, n.userAvatarId); } - path = routeForMessage( - "direct_chat", - { chatId: n.them }, - n.messageIndex, - ); + path = routeForMessage("direct_chat", { chatId: n.them }, n.messageIndex); tag = path; timestamp = Number(n.timestamp); } else if (n.kind === "channel_reaction") { @@ -243,10 +239,55 @@ async function showNotification(n: Notification, id: string): Promise { ); tag = path; timestamp = Number(n.timestamp); + } else if (n.kind === "direct_message_tipped") { + title = n.username; + body = `${n.displayName ?? n.username} tipped your message ${n.tip}`; + if (n.userAvatarId !== undefined) { + icon = avatarUrl(n.them.userId, n.userAvatarId); + } + path = routeForMessage("direct_chat", { chatId: n.them }, n.messageIndex); + tag = path; + timestamp = Number(n.timestamp); + } else if (n.kind === "channel_message_tipped") { + title = `${n.communityName} / ${n.channelName}`; + body = `${n.tippedByDisplayName ?? n.tippedByName} tipped your message ${n.tip}`; + if (n.channelAvatarId !== undefined) { + icon = channelAvatarUrl(n.chatId, n.channelAvatarId); + } else if (n.communityAvatarId !== undefined) { + icon = avatarUrl(n.chatId.communityId, n.communityAvatarId); + } + path = routeForMessage( + "community", + { + chatId: n.chatId, + threadRootMessageIndex: n.threadRootMessageIndex, + }, + n.messageIndex, + ); + tag = path; + timestamp = Number(n.timestamp); + } else if (n.kind === "group_message_tipped") { + title = n.groupName; + body = `${n.tippedByDisplayName ?? n.tippedByName} tipped your message ${n.tip}`; + if (n.groupAvatarId !== undefined) { + icon = avatarUrl(n.chatId.groupId, n.groupAvatarId); + } + path = routeForMessage( + "group_chat", + { + chatId: n.chatId, + threadRootMessageIndex: n.threadRootMessageIndex, + }, + n.messageIndex, + ); + tag = path; + timestamp = Number(n.timestamp); } else if (n.kind === "added_to_channel_notification") { // TODO Multi language support title = `${n.communityName} / ${n.channelName}`; - body = `${n.addedByDisplayName ?? n.addedByUsername} added you to the channel "${n.channelName}" in the community "${n.communityName}"`; + body = `${n.addedByDisplayName ?? n.addedByUsername} added you to the channel "${ + n.channelName + }" in the community "${n.communityName}"`; if (n.channelAvatarId !== undefined) { icon = channelAvatarUrl(n.chatId, n.channelAvatarId); } else if (n.communityAvatarId !== undefined) { diff --git a/frontend/openchat-shared/src/domain/notifications/index.ts b/frontend/openchat-shared/src/domain/notifications/index.ts index 2f5a4835b1..38c00d70a2 100644 --- a/frontend/openchat-shared/src/domain/notifications/index.ts +++ b/frontend/openchat-shared/src/domain/notifications/index.ts @@ -1,8 +1,4 @@ -import type { - ChannelIdentifier, - DirectChatIdentifier, - GroupChatIdentifier, -} from "../chat/chat"; +import type { ChannelIdentifier, DirectChatIdentifier, GroupChatIdentifier } from "../chat/chat"; export type Notification = | AddedToChannelNotification @@ -11,7 +7,10 @@ export type Notification = | GroupNotification | ChannelReaction | DirectReaction - | GroupReaction; + | GroupReaction + | ChannelMessageTipped + | DirectMessageTipped + | GroupMessageTipped; export type AddedToChannelNotification = { kind: "added_to_channel_notification"; @@ -123,6 +122,50 @@ export type GroupReaction = { timestamp: bigint; }; +export type ChannelMessageTipped = { + kind: "channel_message_tipped"; + chatId: ChannelIdentifier; + threadRootMessageIndex: number | undefined; + messageIndex: number; + messageEventIndex: number; + communityName: string; + channelName: string; + tippedBy: string; + tippedByName: string; + tippedByDisplayName: string | undefined; + tip: string; + communityAvatarId: bigint | undefined; + channelAvatarId: bigint | undefined; + timestamp: bigint; +}; + +export type DirectMessageTipped = { + kind: "direct_message_tipped"; + messageIndex: number; + messageEventIndex: number; + them: DirectChatIdentifier; + username: string; + displayName: string | undefined; + tip: string; + userAvatarId: bigint | undefined; + timestamp: bigint; +}; + +export type GroupMessageTipped = { + kind: "group_message_tipped"; + chatId: GroupChatIdentifier; + threadRootMessageIndex: number | undefined; + messageIndex: number; + messageEventIndex: number; + groupName: string; + tippedBy: string; + tippedByName: string; + tippedByDisplayName: string | undefined; + tip: string; + groupAvatarId: bigint | undefined; + timestamp: bigint; +}; + export type CryptoTransferDetails = { recipient: string; recipientUsername: string | undefined;