Some checks failed
Deploy Web Apps / deploy (push) Successful in 5m52s
Build Android APK / build-android-apk (push) Failing after 23m15s
Queue Release Build / prepare (push) Successful in 1m42s
Queue Release Build / build-linux (push) Failing after 9m33s
Queue Release Build / build-windows (push) Successful in 26m5s
Queue Release Build / finalize (push) Has been skipped
166 lines
4.9 KiB
TypeScript
166 lines
4.9 KiB
TypeScript
import type { Message, Room } from '../../../../shared-kernel';
|
|
import type {
|
|
NotificationDeliveryContext,
|
|
NotificationDisplayPayload,
|
|
NotificationsSettings,
|
|
RoomUnreadCounts
|
|
} from '../models/notification.model';
|
|
|
|
export const DEFAULT_TEXT_CHANNEL_ID = 'general';
|
|
|
|
const MESSAGE_PREVIEW_LIMIT = 140;
|
|
|
|
export type AppTranslateFn = (key: string, params?: Record<string, string | number>) => string;
|
|
|
|
export function resolveMessageChannelId(message: Pick<Message, 'channelId'>): string {
|
|
return message.channelId || DEFAULT_TEXT_CHANNEL_ID;
|
|
}
|
|
|
|
export function getRoomTextChannelIds(room: Room): string[] {
|
|
const textChannelIds = (room.channels ?? [])
|
|
.filter((channel) => channel.type === 'text')
|
|
.map((channel) => channel.id);
|
|
|
|
return textChannelIds.length > 0 ? textChannelIds : [DEFAULT_TEXT_CHANNEL_ID];
|
|
}
|
|
|
|
export function getRoomById(rooms: Room[], roomId: string): Room | null {
|
|
return rooms.find((room) => room.id === roomId) ?? null;
|
|
}
|
|
|
|
export function getChannelLabel(room: Room | null, channelId: string): string {
|
|
const channelName = room?.channels?.find((channel) => channel.id === channelId)?.name;
|
|
|
|
return channelName || DEFAULT_TEXT_CHANNEL_ID;
|
|
}
|
|
|
|
export function getChannelLastReadAt(
|
|
settings: NotificationsSettings,
|
|
roomId: string,
|
|
channelId: string
|
|
): number {
|
|
return settings.lastReadByChannel[roomId]?.[channelId]
|
|
?? settings.roomBaselines[roomId]
|
|
?? 0;
|
|
}
|
|
|
|
export function getRoomTrackingBaseline(settings: NotificationsSettings, room: Room): number {
|
|
const trackedChannels = getRoomTextChannelIds(room).map((channelId) =>
|
|
getChannelLastReadAt(settings, room.id, channelId)
|
|
);
|
|
|
|
return Math.min(...trackedChannels, settings.roomBaselines[room.id] ?? Date.now());
|
|
}
|
|
|
|
export function isRoomMuted(settings: NotificationsSettings, roomId: string): boolean {
|
|
return settings.mutedRooms[roomId] === true;
|
|
}
|
|
|
|
export function isChannelMuted(
|
|
settings: NotificationsSettings,
|
|
roomId: string,
|
|
channelId: string
|
|
): boolean {
|
|
return settings.mutedChannels[roomId]?.[channelId] === true;
|
|
}
|
|
|
|
export function isMessageVisibleInActiveView(
|
|
message: Pick<Message, 'channelId' | 'roomId'>,
|
|
context: NotificationDeliveryContext
|
|
): boolean {
|
|
return context.currentRoomId === message.roomId
|
|
&& context.activeChannelId === resolveMessageChannelId(message)
|
|
&& context.isWindowFocused
|
|
&& context.isDocumentVisible;
|
|
}
|
|
|
|
export function shouldDeliverNotification(
|
|
settings: NotificationsSettings,
|
|
message: Pick<Message, 'channelId' | 'roomId'>,
|
|
context: NotificationDeliveryContext
|
|
): boolean {
|
|
const channelId = resolveMessageChannelId(message);
|
|
|
|
if (!settings.enabled) {
|
|
return false;
|
|
}
|
|
|
|
if (context.currentUser?.status === 'busy') {
|
|
return false;
|
|
}
|
|
|
|
if (isRoomMuted(settings, message.roomId) || isChannelMuted(settings, message.roomId, channelId)) {
|
|
return false;
|
|
}
|
|
|
|
return !isMessageVisibleInActiveView(message, context);
|
|
}
|
|
|
|
export function buildNotificationDisplayPayload(
|
|
message: Pick<Message, 'channelId' | 'content' | 'senderName'>,
|
|
room: Room | null,
|
|
settings: NotificationsSettings,
|
|
requestAttention: boolean,
|
|
translate: AppTranslateFn
|
|
): NotificationDisplayPayload {
|
|
const channelId = resolveMessageChannelId(message);
|
|
const roomName = room?.name || translate('notifications.display.defaultServerName');
|
|
const channelLabel = getChannelLabel(room, channelId);
|
|
|
|
return {
|
|
title: `${roomName} · #${channelLabel}`,
|
|
body: settings.showPreview
|
|
? formatMessagePreview(message.senderName, message.content, translate)
|
|
: translate('notifications.display.newMessageHidden', { sender: message.senderName }),
|
|
requestAttention
|
|
};
|
|
}
|
|
|
|
export function calculateUnreadForRoom(
|
|
room: Room,
|
|
messages: Message[],
|
|
settings: NotificationsSettings,
|
|
currentUserIds: Set<string>
|
|
): RoomUnreadCounts {
|
|
const channelCounts = Object.fromEntries(
|
|
getRoomTextChannelIds(room).map((channelId) => [channelId, 0])
|
|
) as Record<string, number>;
|
|
|
|
for (const message of messages) {
|
|
if (message.isDeleted || currentUserIds.has(message.senderId)) {
|
|
continue;
|
|
}
|
|
|
|
const channelId = resolveMessageChannelId(message);
|
|
|
|
if (!(channelId in channelCounts)) {
|
|
continue;
|
|
}
|
|
|
|
if (message.timestamp <= getChannelLastReadAt(settings, room.id, channelId)) {
|
|
continue;
|
|
}
|
|
|
|
channelCounts[channelId] += 1;
|
|
}
|
|
|
|
return {
|
|
channelCounts,
|
|
roomCount: Object.values(channelCounts).reduce((total, count) => total + count, 0)
|
|
};
|
|
}
|
|
|
|
function formatMessagePreview(senderName: string, content: string, translate: AppTranslateFn): string {
|
|
const normalisedContent = content.replace(/\s+/g, ' ').trim();
|
|
|
|
if (!normalisedContent) {
|
|
return translate('notifications.display.newMessageEmpty', { sender: senderName });
|
|
}
|
|
|
|
const preview = normalisedContent.length > MESSAGE_PREVIEW_LIMIT
|
|
? `${normalisedContent.slice(0, MESSAGE_PREVIEW_LIMIT - 1)}...`
|
|
: normalisedContent;
|
|
|
|
return translate('notifications.display.preview', { sender: senderName, content: preview });
|
|
}
|