Notifications: Support reactions (#1709)
This commit is contained in:
parent
611ec32ee8
commit
e701bf9836
@ -534,7 +534,7 @@ export type ActionTypes = (
|
|||||||
'loadSponsoredMessages' | 'viewSponsoredMessage' | 'loadSendAs' | 'saveDefaultSendAs' | 'loadAvailableReactions' |
|
'loadSponsoredMessages' | 'viewSponsoredMessage' | 'loadSendAs' | 'saveDefaultSendAs' | 'loadAvailableReactions' |
|
||||||
'stopActiveEmojiInteraction' | 'interactWithAnimatedEmoji' | 'loadReactors' | 'setDefaultReaction' |
|
'stopActiveEmojiInteraction' | 'interactWithAnimatedEmoji' | 'loadReactors' | 'setDefaultReaction' |
|
||||||
'sendDefaultReaction' | 'sendEmojiInteraction' | 'sendWatchingEmojiInteraction' | 'loadMessageReactions' |
|
'sendDefaultReaction' | 'sendEmojiInteraction' | 'sendWatchingEmojiInteraction' | 'loadMessageReactions' |
|
||||||
'stopActiveReaction' |
|
'stopActiveReaction' | 'startActiveReaction' |
|
||||||
// downloads
|
// downloads
|
||||||
'downloadSelectedMessages' | 'downloadMessageMedia' | 'cancelMessageMediaDownload' |
|
'downloadSelectedMessages' | 'downloadMessageMedia' | 'cancelMessageMediaDownload' |
|
||||||
// scheduled messages
|
// scheduled messages
|
||||||
|
|||||||
@ -166,6 +166,30 @@ addReducer('openChat', (global) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addReducer('startActiveReaction', (global, actions, payload) => {
|
||||||
|
const { messageId, reaction } = payload;
|
||||||
|
const { animationLevel } = global.settings.byKey;
|
||||||
|
|
||||||
|
if (animationLevel !== ANIMATION_LEVEL_MAX) return global;
|
||||||
|
|
||||||
|
if (global.activeReactions[messageId]?.reaction === reaction) {
|
||||||
|
return global;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...global,
|
||||||
|
activeReactions: {
|
||||||
|
...(reaction ? global.activeReactions : omit(global.activeReactions, [messageId])),
|
||||||
|
...(reaction && {
|
||||||
|
[messageId]: {
|
||||||
|
reaction,
|
||||||
|
messageId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
addReducer('stopActiveReaction', (global, actions, payload) => {
|
addReducer('stopActiveReaction', (global, actions, payload) => {
|
||||||
const { messageId, reaction } = payload;
|
const { messageId, reaction } = payload;
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,8 @@ import { ApiUpdate, MAIN_THREAD_ID } from '../../../api/types';
|
|||||||
|
|
||||||
import { ARCHIVED_FOLDER_ID, MAX_ACTIVE_PINNED_CHATS } from '../../../config';
|
import { ARCHIVED_FOLDER_ID, MAX_ACTIVE_PINNED_CHATS } from '../../../config';
|
||||||
import { pick } from '../../../util/iteratees';
|
import { pick } from '../../../util/iteratees';
|
||||||
import { closeMessageNotifications, notifyAboutNewMessage } from '../../../util/notifications';
|
import { closeMessageNotifications, notifyAboutMessage } from '../../../util/notifications';
|
||||||
|
import { getMessageRecentReaction } from '../../helpers';
|
||||||
import {
|
import {
|
||||||
updateChat,
|
updateChat,
|
||||||
updateChatListIds,
|
updateChatListIds,
|
||||||
@ -120,7 +121,7 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyAboutNewMessage({
|
notifyAboutMessage({
|
||||||
chat,
|
chat,
|
||||||
message,
|
message,
|
||||||
});
|
});
|
||||||
@ -128,6 +129,22 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'updateMessage': {
|
||||||
|
const { message } = update;
|
||||||
|
const chat = selectChat(global, update.chatId);
|
||||||
|
if (!chat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getMessageRecentReaction(message)) {
|
||||||
|
notifyAboutMessage({
|
||||||
|
chat,
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'updateCommonBoxMessages':
|
case 'updateCommonBoxMessages':
|
||||||
case 'updateChannelMessages': {
|
case 'updateChannelMessages': {
|
||||||
const { ids, messageUpdate } = update;
|
const { ids, messageUpdate } = update;
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import {
|
|||||||
|
|
||||||
import { unique } from '../../../util/iteratees';
|
import { unique } from '../../../util/iteratees';
|
||||||
import { areDeepEqual } from '../../../util/areDeepEqual';
|
import { areDeepEqual } from '../../../util/areDeepEqual';
|
||||||
|
import { notifyAboutMessage } from '../../../util/notifications';
|
||||||
|
import { checkIfReactionAdded } from '../../helpers/reactions';
|
||||||
import {
|
import {
|
||||||
updateChat,
|
updateChat,
|
||||||
deleteChatMessages,
|
deleteChatMessages,
|
||||||
@ -479,6 +481,7 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
|||||||
case 'updateMessageReactions': {
|
case 'updateMessageReactions': {
|
||||||
const { chatId, id, reactions } = update;
|
const { chatId, id, reactions } = update;
|
||||||
const message = selectChatMessage(global, chatId, id);
|
const message = selectChatMessage(global, chatId, id);
|
||||||
|
const chat = selectChat(global, update.chatId);
|
||||||
const currentReactions = message?.reactions;
|
const currentReactions = message?.reactions;
|
||||||
|
|
||||||
// `updateMessageReactions` happens with an interval so we try to avoid redundant global state updates
|
// `updateMessageReactions` happens with an interval so we try to avoid redundant global state updates
|
||||||
@ -486,8 +489,21 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setGlobal(updateChatMessage(global, chatId, id, { reactions: update.reactions }));
|
// Only notify about added reactions, not removed ones
|
||||||
|
const shouldNotify = checkIfReactionAdded(currentReactions, reactions);
|
||||||
|
|
||||||
|
global = updateChatMessage(global, chatId, id, { reactions: update.reactions });
|
||||||
|
|
||||||
|
if (shouldNotify) {
|
||||||
|
const newMessage = selectChatMessage(global, chatId, id);
|
||||||
|
if (!chat || !newMessage) return;
|
||||||
|
notifyAboutMessage({
|
||||||
|
chat,
|
||||||
|
message: newMessage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setGlobal(global);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,3 +5,4 @@ export * from './messageSummary';
|
|||||||
export * from './messageMedia';
|
export * from './messageMedia';
|
||||||
export * from './localSearch';
|
export * from './localSearch';
|
||||||
export * from './payments';
|
export * from './payments';
|
||||||
|
export { getMessageRecentReaction } from './reactions';
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
|
import { ApiMessage, ApiMessageEntityTypes } from '../../api/types';
|
||||||
import type { TextPart } from '../../components/common/helpers/renderTextWithEntities';
|
import type { TextPart } from '../../components/common/helpers/renderTextWithEntities';
|
||||||
|
import { CONTENT_NOT_SUPPORTED } from '../../config';
|
||||||
|
|
||||||
import { LangFn } from '../../hooks/useLang';
|
import { LangFn } from '../../hooks/useLang';
|
||||||
import { ApiMessage, ApiMessageEntityTypes } from '../../api/types';
|
|
||||||
import { CONTENT_NOT_SUPPORTED } from '../../config';
|
|
||||||
import { getMessageText } from './messages';
|
|
||||||
import trimText from '../../util/trimText';
|
import trimText from '../../util/trimText';
|
||||||
|
import { getMessageText } from './messages';
|
||||||
|
import { getMessageRecentReaction } from './reactions';
|
||||||
|
|
||||||
const SPOILER_CHARS = ['⠺', '⠵', '⠞', '⠟'];
|
const SPOILER_CHARS = ['⠺', '⠵', '⠞', '⠟'];
|
||||||
export const TRUNCATED_SUMMARY_LENGTH = 80;
|
export const TRUNCATED_SUMMARY_LENGTH = 80;
|
||||||
@ -14,11 +15,12 @@ export function getMessageSummaryText(
|
|||||||
message: ApiMessage,
|
message: ApiMessage,
|
||||||
noEmoji = false,
|
noEmoji = false,
|
||||||
truncateLength = TRUNCATED_SUMMARY_LENGTH,
|
truncateLength = TRUNCATED_SUMMARY_LENGTH,
|
||||||
|
noReactions = true,
|
||||||
) {
|
) {
|
||||||
const emoji = !noEmoji && getMessageSummaryEmoji(message);
|
const emoji = !noEmoji && getMessageSummaryEmoji(message, noReactions);
|
||||||
const emojiWithSpace = emoji ? `${emoji} ` : '';
|
const emojiWithSpace = emoji ? `${emoji} ` : '';
|
||||||
const text = trimText(getMessageTextWithSpoilers(message), truncateLength);
|
const text = trimText(getMessageTextWithSpoilers(message), truncateLength);
|
||||||
const description = getMessageSummaryDescription(lang, message, text);
|
const description = getMessageSummaryDescription(lang, message, text, noReactions);
|
||||||
|
|
||||||
return `${emojiWithSpace}${description}`;
|
return `${emojiWithSpace}${description}`;
|
||||||
}
|
}
|
||||||
@ -34,7 +36,11 @@ export function getMessageTextWithSpoilers(message: ApiMessage) {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
return entities.reduce((accText, { type, offset, length }) => {
|
return entities.reduce((accText, {
|
||||||
|
type,
|
||||||
|
offset,
|
||||||
|
length,
|
||||||
|
}) => {
|
||||||
if (type !== ApiMessageEntityTypes.Spoiler) {
|
if (type !== ApiMessageEntityTypes.Spoiler) {
|
||||||
return accText;
|
return accText;
|
||||||
}
|
}
|
||||||
@ -46,9 +52,15 @@ export function getMessageTextWithSpoilers(message: ApiMessage) {
|
|||||||
}, text);
|
}, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageSummaryEmoji(message: ApiMessage) {
|
export function getMessageSummaryEmoji(message: ApiMessage, noReactions = true) {
|
||||||
const {
|
const {
|
||||||
photo, video, audio, voice, document, sticker, poll,
|
photo,
|
||||||
|
video,
|
||||||
|
audio,
|
||||||
|
voice,
|
||||||
|
document,
|
||||||
|
sticker,
|
||||||
|
poll,
|
||||||
} = message.content;
|
} = message.content;
|
||||||
|
|
||||||
if (message.groupedId || photo) {
|
if (message.groupedId || photo) {
|
||||||
@ -79,59 +91,86 @@ export function getMessageSummaryEmoji(message: ApiMessage) {
|
|||||||
return '📊';
|
return '📊';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reaction = !noReactions && getMessageRecentReaction(message);
|
||||||
|
if (reaction) {
|
||||||
|
return reaction.reaction;
|
||||||
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageSummaryDescription(lang: LangFn, message: ApiMessage, truncatedText?: string | TextPart[]) {
|
export function getMessageSummaryDescription(
|
||||||
|
lang: LangFn,
|
||||||
|
message: ApiMessage,
|
||||||
|
truncatedText?: string | TextPart[],
|
||||||
|
noReactions = true,
|
||||||
|
) {
|
||||||
const {
|
const {
|
||||||
text, photo, video, audio, voice, document, sticker, contact, poll, invoice,
|
text,
|
||||||
|
photo,
|
||||||
|
video,
|
||||||
|
audio,
|
||||||
|
voice,
|
||||||
|
document,
|
||||||
|
sticker,
|
||||||
|
contact,
|
||||||
|
poll,
|
||||||
|
invoice,
|
||||||
} = message.content;
|
} = message.content;
|
||||||
|
|
||||||
|
let summary: string | TextPart[] | undefined;
|
||||||
|
|
||||||
if (message.groupedId) {
|
if (message.groupedId) {
|
||||||
return truncatedText || lang('lng_in_dlg_album');
|
summary = truncatedText || lang('lng_in_dlg_album');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (photo) {
|
if (photo) {
|
||||||
return truncatedText || lang('AttachPhoto');
|
summary = truncatedText || lang('AttachPhoto');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (video) {
|
if (video) {
|
||||||
return truncatedText || lang(video.isGif ? 'AttachGif' : 'AttachVideo');
|
summary = truncatedText || lang(video.isGif ? 'AttachGif' : 'AttachVideo');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sticker) {
|
if (sticker) {
|
||||||
return lang('AttachSticker').trim();
|
summary = lang('AttachSticker')
|
||||||
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audio) {
|
if (audio) {
|
||||||
return getMessageAudioCaption(message) || lang('AttachMusic');
|
summary = getMessageAudioCaption(message) || lang('AttachMusic');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (voice) {
|
if (voice) {
|
||||||
return truncatedText || lang('AttachAudio');
|
summary = truncatedText || lang('AttachAudio');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document) {
|
if (document) {
|
||||||
return truncatedText || document.fileName;
|
summary = truncatedText || document.fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contact) {
|
if (contact) {
|
||||||
return lang('AttachContact');
|
summary = lang('AttachContact');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (poll) {
|
if (poll) {
|
||||||
return poll.summary.question;
|
summary = poll.summary.question;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (invoice) {
|
if (invoice) {
|
||||||
return 'Invoice';
|
summary = 'Invoice';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text) {
|
if (text) {
|
||||||
return truncatedText;
|
summary = truncatedText;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CONTENT_NOT_SUPPORTED;
|
const reaction = !noReactions && getMessageRecentReaction(message);
|
||||||
|
if (summary && reaction) {
|
||||||
|
summary = `to your "${summary}"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return summary || CONTENT_NOT_SUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateBrailleSpoiler(length: number) {
|
export function generateBrailleSpoiler(length: number) {
|
||||||
@ -142,7 +181,11 @@ export function generateBrailleSpoiler(length: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getMessageAudioCaption(message: ApiMessage) {
|
function getMessageAudioCaption(message: ApiMessage) {
|
||||||
const { audio, text } = message.content;
|
const {
|
||||||
|
audio,
|
||||||
|
text,
|
||||||
|
} = message.content;
|
||||||
|
|
||||||
return (audio && [audio.title, audio.performer].filter(Boolean).join(' — ')) || (text?.text);
|
return (audio && [audio.title, audio.performer].filter(Boolean)
|
||||||
|
.join(' — ')) || (text?.text);
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/modules/helpers/reactions.ts
Normal file
15
src/modules/helpers/reactions.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { ApiMessage, ApiReactions } from '../../api/types';
|
||||||
|
|
||||||
|
export function getMessageRecentReaction(message: Partial<ApiMessage>) {
|
||||||
|
return message.isOutgoing ? message.reactions?.recentReactions?.[0] : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkIfReactionAdded(oldReactions?: ApiReactions, newReactions?: ApiReactions) {
|
||||||
|
if (!oldReactions || !oldReactions.recentReactions) return true;
|
||||||
|
if (!newReactions || !newReactions.recentReactions) return false;
|
||||||
|
const oldReactionsMap = oldReactions.results.reduce<Record<string, number>>((acc, reaction) => {
|
||||||
|
acc[reaction.reaction] = reaction.count;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
return newReactions.results.some(r => !oldReactionsMap[r.reaction] || oldReactionsMap[r.reaction] < r.count);
|
||||||
|
}
|
||||||
@ -29,6 +29,13 @@ type NotificationData = {
|
|||||||
title: string;
|
title: string;
|
||||||
body: string;
|
body: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
reaction?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FocusMessageData = {
|
||||||
|
chatId?: string;
|
||||||
|
messageId?: number;
|
||||||
|
reaction?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type CloseNotificationData = {
|
type CloseNotificationData = {
|
||||||
@ -103,6 +110,7 @@ function showNotification({
|
|||||||
body,
|
body,
|
||||||
title,
|
title,
|
||||||
icon,
|
icon,
|
||||||
|
reaction,
|
||||||
}: NotificationData) {
|
}: NotificationData) {
|
||||||
const isFirstBatch = new Date().valueOf() - lastSyncAt < 1000;
|
const isFirstBatch = new Date().valueOf() - lastSyncAt < 1000;
|
||||||
const tag = String(isFirstBatch ? 0 : chatId || 0);
|
const tag = String(isFirstBatch ? 0 : chatId || 0);
|
||||||
@ -111,6 +119,7 @@ function showNotification({
|
|||||||
data: {
|
data: {
|
||||||
chatId,
|
chatId,
|
||||||
messageId,
|
messageId,
|
||||||
|
reaction,
|
||||||
count: 1,
|
count: 1,
|
||||||
},
|
},
|
||||||
icon: icon || 'icon-192x192.png',
|
icon: icon || 'icon-192x192.png',
|
||||||
@ -158,7 +167,7 @@ export function handlePush(e: PushEvent) {
|
|||||||
|
|
||||||
const notification = getNotificationData(data);
|
const notification = getNotificationData(data);
|
||||||
|
|
||||||
// Dont show already triggered notification
|
// Don't show already triggered notification
|
||||||
if (shownNotifications.has(notification.messageId)) {
|
if (shownNotifications.has(notification.messageId)) {
|
||||||
shownNotifications.delete(notification.messageId);
|
shownNotifications.delete(notification.messageId);
|
||||||
return;
|
return;
|
||||||
@ -167,18 +176,11 @@ export function handlePush(e: PushEvent) {
|
|||||||
e.waitUntil(showNotification(notification));
|
e.waitUntil(showNotification(notification));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function focusChatMessage(client: WindowClient, data: { chatId?: string; messageId?: number }) {
|
async function focusChatMessage(client: WindowClient, data: FocusMessageData) {
|
||||||
const {
|
if (!data.chatId) return;
|
||||||
chatId,
|
|
||||||
messageId,
|
|
||||||
} = data;
|
|
||||||
if (!chatId) return;
|
|
||||||
client.postMessage({
|
client.postMessage({
|
||||||
type: 'focusMessage',
|
type: 'focusMessage',
|
||||||
payload: {
|
payload: data,
|
||||||
chatId,
|
|
||||||
messageId,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
if (!client.focused) {
|
if (!client.focused) {
|
||||||
// Catch "focus not allowed" DOM Exceptions
|
// Catch "focus not allowed" DOM Exceptions
|
||||||
@ -240,12 +242,19 @@ export function handleClientMessage(e: ExtendableMessageEvent) {
|
|||||||
e.waitUntil(focusChatMessage(source, data));
|
e.waitUntil(focusChatMessage(source, data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (e.data.type === 'newMessageNotification') {
|
if (e.data.type === 'showMessageNotification') {
|
||||||
// store messageId for already shown notification
|
// store messageId for already shown notification
|
||||||
const notification: NotificationData = e.data.payload;
|
const notification: NotificationData = e.data.payload;
|
||||||
// mark this notification as shown if it was handled locally
|
e.waitUntil((async () => {
|
||||||
shownNotifications.add(notification.messageId);
|
// Close existing notification if it is already shown
|
||||||
e.waitUntil(showNotification(notification));
|
if (notification.chatId) {
|
||||||
|
const notifications = await self.registration.getNotifications({ tag: notification.chatId });
|
||||||
|
notifications.forEach((n) => n.close());
|
||||||
|
}
|
||||||
|
// Mark this notification as shown if it was handled locally
|
||||||
|
shownNotifications.add(notification.messageId);
|
||||||
|
return showNotification(notification);
|
||||||
|
})());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.data.type === 'closeMessageNotifications') {
|
if (e.data.type === 'closeMessageNotifications') {
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import { callApi } from '../api/gramjs';
|
import { callApi } from '../api/gramjs';
|
||||||
import {
|
import { ApiChat, ApiMediaFormat, ApiMessage, ApiUser, ApiUserReaction } from '../api/types';
|
||||||
ApiChat, ApiMediaFormat, ApiMessage, ApiUser,
|
|
||||||
} from '../api/types';
|
|
||||||
import { renderActionMessageText } from '../components/common/helpers/renderActionMessageText';
|
import { renderActionMessageText } from '../components/common/helpers/renderActionMessageText';
|
||||||
import { DEBUG, IS_TEST } from '../config';
|
import { DEBUG, IS_TEST } from '../config';
|
||||||
import { getDispatch, getGlobal, setGlobal } from '../lib/teact/teactn';
|
import { getDispatch, getGlobal, setGlobal } from '../lib/teact/teactn';
|
||||||
@ -9,19 +7,25 @@ import {
|
|||||||
getChatAvatarHash,
|
getChatAvatarHash,
|
||||||
getChatTitle,
|
getChatTitle,
|
||||||
getMessageAction,
|
getMessageAction,
|
||||||
|
getMessageRecentReaction,
|
||||||
getMessageSenderName,
|
getMessageSenderName,
|
||||||
getMessageSummaryText,
|
getMessageSummaryText,
|
||||||
getPrivateChatUserId,
|
getPrivateChatUserId,
|
||||||
isActionMessage,
|
isActionMessage,
|
||||||
isChatChannel,
|
isChatChannel,
|
||||||
selectIsChatMuted, selectShouldShowMessagePreview,
|
selectIsChatMuted,
|
||||||
|
selectShouldShowMessagePreview,
|
||||||
} from '../modules/helpers';
|
} from '../modules/helpers';
|
||||||
import { getTranslation } from './langProvider';
|
|
||||||
import { addNotifyExceptions, replaceSettings } from '../modules/reducers';
|
import { addNotifyExceptions, replaceSettings } from '../modules/reducers';
|
||||||
import {
|
import {
|
||||||
selectChatMessage, selectNotifyExceptions, selectNotifySettings, selectUser,
|
selectChatMessage,
|
||||||
|
selectCurrentMessageList,
|
||||||
|
selectNotifyExceptions,
|
||||||
|
selectNotifySettings,
|
||||||
|
selectUser,
|
||||||
} from '../modules/selectors';
|
} from '../modules/selectors';
|
||||||
import { IS_SERVICE_WORKER_SUPPORTED } from './environment';
|
import { IS_SERVICE_WORKER_SUPPORTED, IS_TOUCH_ENV } from './environment';
|
||||||
|
import { getTranslation } from './langProvider';
|
||||||
import * as mediaLoader from './mediaLoader';
|
import * as mediaLoader from './mediaLoader';
|
||||||
import { debounce } from './schedulers';
|
import { debounce } from './schedulers';
|
||||||
|
|
||||||
@ -246,16 +250,25 @@ function checkIfShouldNotify(chat: ApiChat) {
|
|||||||
if (isMuted || chat.isNotJoined || !chat.isListed) {
|
if (isMuted || chat.isNotJoined || !chat.isListed) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// On touch devices show notifications when chat is not active
|
||||||
|
if (IS_TOUCH_ENV) {
|
||||||
|
const {
|
||||||
|
chatId,
|
||||||
|
type,
|
||||||
|
} = selectCurrentMessageList(global) || {};
|
||||||
|
return !(chatId === chat.id && type === 'thread');
|
||||||
|
}
|
||||||
|
// On desktop show notifications when window is not focused
|
||||||
return !document.hasFocus();
|
return !document.hasFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNotificationContent(chat: ApiChat, message: ApiMessage) {
|
function getNotificationContent(chat: ApiChat, message: ApiMessage, reaction?: ApiUserReaction) {
|
||||||
const global = getGlobal();
|
const global = getGlobal();
|
||||||
const {
|
let {
|
||||||
senderId,
|
senderId,
|
||||||
replyToMessageId,
|
replyToMessageId,
|
||||||
} = message;
|
} = message;
|
||||||
|
if (reaction) senderId = reaction.userId;
|
||||||
|
|
||||||
const messageSender = senderId ? selectUser(global, senderId) : undefined;
|
const messageSender = senderId ? selectUser(global, senderId) : undefined;
|
||||||
const messageAction = getMessageAction(message as ApiMessage);
|
const messageAction = getMessageAction(message as ApiMessage);
|
||||||
@ -291,7 +304,7 @@ function getNotificationContent(chat: ApiChat, message: ApiMessage) {
|
|||||||
) as string;
|
) as string;
|
||||||
} else {
|
} else {
|
||||||
const senderName = getMessageSenderName(getTranslation, chat.id, messageSender);
|
const senderName = getMessageSenderName(getTranslation, chat.id, messageSender);
|
||||||
const summary = getMessageSummaryText(getTranslation, message);
|
const summary = getMessageSummaryText(getTranslation, message, false, 60, false);
|
||||||
|
|
||||||
body = senderName ? `${senderName}: ${summary}` : summary;
|
body = senderName ? `${senderName}: ${summary}` : summary;
|
||||||
}
|
}
|
||||||
@ -316,7 +329,7 @@ async function getAvatar(chat: ApiChat) {
|
|||||||
return mediaData;
|
return mediaData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function notifyAboutNewMessage({
|
export async function notifyAboutMessage({
|
||||||
chat,
|
chat,
|
||||||
message,
|
message,
|
||||||
}: { chat: ApiChat; message: Partial<ApiMessage> }) {
|
}: { chat: ApiChat; message: Partial<ApiMessage> }) {
|
||||||
@ -329,26 +342,29 @@ export async function notifyAboutNewMessage({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!areNotificationsSupported) return;
|
if (!areNotificationsSupported) return;
|
||||||
|
|
||||||
if (!message.id) return;
|
if (!message.id) return;
|
||||||
|
|
||||||
|
const activeReaction = getMessageRecentReaction(message);
|
||||||
|
const icon = await getAvatar(chat);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
body,
|
body,
|
||||||
} = getNotificationContent(chat, message as ApiMessage);
|
} = getNotificationContent(chat, message as ApiMessage, activeReaction);
|
||||||
|
|
||||||
const icon = await getAvatar(chat);
|
|
||||||
|
|
||||||
if (checkIfPushSupported()) {
|
if (checkIfPushSupported()) {
|
||||||
if (navigator.serviceWorker?.controller) {
|
if (navigator.serviceWorker?.controller) {
|
||||||
// notify service worker about new message notification
|
// notify service worker about new message notification
|
||||||
navigator.serviceWorker.controller.postMessage({
|
navigator.serviceWorker.controller.postMessage({
|
||||||
type: 'newMessageNotification',
|
type: 'showMessageNotification',
|
||||||
payload: {
|
payload: {
|
||||||
title,
|
title,
|
||||||
body,
|
body,
|
||||||
icon,
|
icon,
|
||||||
chatId: chat.id,
|
chatId: chat.id,
|
||||||
messageId: message.id,
|
messageId: message.id,
|
||||||
|
reaction: activeReaction ? activeReaction.reaction : undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -373,6 +389,12 @@ export async function notifyAboutNewMessage({
|
|||||||
chatId: chat.id,
|
chatId: chat.id,
|
||||||
messageId: message.id,
|
messageId: message.id,
|
||||||
});
|
});
|
||||||
|
if (activeReaction) {
|
||||||
|
dispatch.startActiveReaction({
|
||||||
|
messageId: message.id,
|
||||||
|
reaction: activeReaction.reaction,
|
||||||
|
});
|
||||||
|
}
|
||||||
if (window.focus) {
|
if (window.focus) {
|
||||||
window.focus();
|
window.focus();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,10 +16,17 @@ function handleWorkerMessage(e: MessageEvent) {
|
|||||||
}
|
}
|
||||||
if (!action.type) return;
|
if (!action.type) return;
|
||||||
const dispatch = getDispatch();
|
const dispatch = getDispatch();
|
||||||
|
const payload = action.payload;
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'focusMessage':
|
case 'focusMessage':
|
||||||
if (dispatch.focusMessage) {
|
if (dispatch.focusMessage) {
|
||||||
dispatch.focusMessage(action.payload);
|
dispatch.focusMessage(payload);
|
||||||
|
}
|
||||||
|
if (dispatch.startActiveReaction && payload.reaction) {
|
||||||
|
dispatch.startActiveReaction({
|
||||||
|
messageId: payload.messageId,
|
||||||
|
reaction: payload.reaction,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'playNotificationSound':
|
case 'playNotificationSound':
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user