mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 14:03:30 +00:00 
			
		
		
		
	notifications: Split out message_notifications module.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
		
				
					committed by
					
						
						Tim Abbott
					
				
			
			
				
	
			
			
			
						parent
						
							e48771993b
						
					
				
				
					commit
					db20fd12e0
				
			@@ -133,6 +133,7 @@ EXEMPT_FILES = make_set(
 | 
			
		||||
        "web/src/message_list_view.js",
 | 
			
		||||
        "web/src/message_lists.js",
 | 
			
		||||
        "web/src/message_live_update.js",
 | 
			
		||||
        "web/src/message_notifications.js",
 | 
			
		||||
        "web/src/message_scroll.js",
 | 
			
		||||
        "web/src/message_scroll_state.ts",
 | 
			
		||||
        "web/src/message_util.js",
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ import * as message_edit from "./message_edit";
 | 
			
		||||
import * as message_edit_history from "./message_edit_history";
 | 
			
		||||
import * as message_helper from "./message_helper";
 | 
			
		||||
import * as message_lists from "./message_lists";
 | 
			
		||||
import * as message_notifications from "./message_notifications";
 | 
			
		||||
import * as message_store from "./message_store";
 | 
			
		||||
import * as message_util from "./message_util";
 | 
			
		||||
import * as narrow from "./narrow";
 | 
			
		||||
@@ -161,7 +162,7 @@ export function insert_new_messages(messages, sent_by_this_client) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    unread_ops.process_visible();
 | 
			
		||||
    notifications.received_messages(messages);
 | 
			
		||||
    message_notifications.received_messages(messages);
 | 
			
		||||
    stream_list.update_streams_sidebar();
 | 
			
		||||
    pm_list.update_private_messages();
 | 
			
		||||
    recent_view_ui.process_messages(messages);
 | 
			
		||||
@@ -482,7 +483,7 @@ export function update_messages(events) {
 | 
			
		||||
                anchor_message.last_edit_timestamp = event.edit_timestamp;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            notifications.received_messages([anchor_message]);
 | 
			
		||||
            message_notifications.received_messages([anchor_message]);
 | 
			
		||||
            alert_words.process_message(anchor_message);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										404
									
								
								web/src/message_notifications.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										404
									
								
								web/src/message_notifications.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,404 @@
 | 
			
		||||
import $ from "jquery";
 | 
			
		||||
 | 
			
		||||
import * as alert_words from "./alert_words";
 | 
			
		||||
import * as blueslip from "./blueslip";
 | 
			
		||||
import {$t} from "./i18n";
 | 
			
		||||
import * as message_parser from "./message_parser";
 | 
			
		||||
import * as narrow from "./narrow";
 | 
			
		||||
import * as notifications from "./notifications";
 | 
			
		||||
import * as people from "./people";
 | 
			
		||||
import * as spoilers from "./spoilers";
 | 
			
		||||
import * as stream_data from "./stream_data";
 | 
			
		||||
import * as ui_util from "./ui_util";
 | 
			
		||||
import * as unread from "./unread";
 | 
			
		||||
import {user_settings} from "./user_settings";
 | 
			
		||||
import * as user_topics from "./user_topics";
 | 
			
		||||
 | 
			
		||||
function get_notification_content(message) {
 | 
			
		||||
    let content;
 | 
			
		||||
    // Convert the content to plain text, replacing emoji with their alt text
 | 
			
		||||
    const $content = $("<div>").html(message.content);
 | 
			
		||||
    ui_util.replace_emoji_with_text($content);
 | 
			
		||||
    spoilers.hide_spoilers_in_notification($content);
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
        $content.text().trim() === "" &&
 | 
			
		||||
        (message_parser.message_has_image(message) ||
 | 
			
		||||
            message_parser.message_has_attachment(message))
 | 
			
		||||
    ) {
 | 
			
		||||
        content = $t({defaultMessage: "(attached file)"});
 | 
			
		||||
    } else {
 | 
			
		||||
        content = $content.text();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (message.is_me_message) {
 | 
			
		||||
        content = message.sender_full_name + content.slice(3);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
        (message.type === "private" || message.type === "test-notification") &&
 | 
			
		||||
        !user_settings.pm_content_in_desktop_notifications
 | 
			
		||||
    ) {
 | 
			
		||||
        content = "New direct message from " + message.sender_full_name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (content.length > 150) {
 | 
			
		||||
        let i;
 | 
			
		||||
        // Truncate content at a word boundary
 | 
			
		||||
        for (i = 150; i > 0; i -= 1) {
 | 
			
		||||
            if (content[i] === " ") {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        content = content.slice(0, i);
 | 
			
		||||
        content += " [...]";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return content;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function debug_notification_source_value(message) {
 | 
			
		||||
    let notification_source;
 | 
			
		||||
 | 
			
		||||
    if (message.type === "private" || message.type === "test-notification") {
 | 
			
		||||
        notification_source = "pm";
 | 
			
		||||
    } else if (message.mentioned) {
 | 
			
		||||
        notification_source = "mention";
 | 
			
		||||
    } else if (message.alerted) {
 | 
			
		||||
        notification_source = "alert";
 | 
			
		||||
    } else {
 | 
			
		||||
        notification_source = "stream";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    blueslip.debug("Desktop notification from source " + notification_source);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function get_notification_key(message) {
 | 
			
		||||
    let key;
 | 
			
		||||
 | 
			
		||||
    if (message.type === "private" || message.type === "test-notification") {
 | 
			
		||||
        key = message.display_reply_to;
 | 
			
		||||
    } else {
 | 
			
		||||
        const stream_name = stream_data.get_stream_name_from_id(message.stream_id);
 | 
			
		||||
        key = message.sender_full_name + " to " + stream_name + " > " + message.topic;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return key;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function remove_sender_from_list_of_recipients(message) {
 | 
			
		||||
    return `, ${message.display_reply_to}, `
 | 
			
		||||
        .replace(`, ${message.sender_full_name}, `, ", ")
 | 
			
		||||
        .slice(", ".length, -", ".length);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function get_notification_title(message, content, msg_count) {
 | 
			
		||||
    let title = message.sender_full_name;
 | 
			
		||||
    let other_recipients;
 | 
			
		||||
 | 
			
		||||
    if (msg_count > 1) {
 | 
			
		||||
        title = msg_count + " messages from " + title;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    switch (message.type) {
 | 
			
		||||
        case "test-notification":
 | 
			
		||||
            other_recipients = remove_sender_from_list_of_recipients(message);
 | 
			
		||||
            break;
 | 
			
		||||
        case "private":
 | 
			
		||||
            other_recipients = remove_sender_from_list_of_recipients(message);
 | 
			
		||||
            if (message.display_recipient.length > 2) {
 | 
			
		||||
                // If the message has too many recipients to list them all...
 | 
			
		||||
                if (content.length + title.length + other_recipients.length > 230) {
 | 
			
		||||
                    // Then count how many people are in the conversation and summarize
 | 
			
		||||
                    // by saying the conversation is with "you and [number] other people"
 | 
			
		||||
                    other_recipients =
 | 
			
		||||
                        other_recipients.replaceAll(/[^,]/g, "").length + " other people";
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                title += " (to you and " + other_recipients + ")";
 | 
			
		||||
            } else {
 | 
			
		||||
                title += " (to you)";
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        case "stream": {
 | 
			
		||||
            const stream_name = stream_data.get_stream_name_from_id(message.stream_id);
 | 
			
		||||
            title += " (to " + stream_name + " > " + message.topic + ")";
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return title;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function process_notification(notification) {
 | 
			
		||||
    const message = notification.message;
 | 
			
		||||
    const content = get_notification_content(message);
 | 
			
		||||
    const key = get_notification_key(message);
 | 
			
		||||
    let notification_object;
 | 
			
		||||
    let msg_count = 1;
 | 
			
		||||
 | 
			
		||||
    debug_notification_source_value(message);
 | 
			
		||||
 | 
			
		||||
    if (notifications.notice_memory.has(key)) {
 | 
			
		||||
        msg_count = notifications.notice_memory.get(key).msg_count + 1;
 | 
			
		||||
        notification_object = notifications.notice_memory.get(key).obj;
 | 
			
		||||
        notification_object.close();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const title = get_notification_title(message, content, msg_count);
 | 
			
		||||
 | 
			
		||||
    if (notification.desktop_notify) {
 | 
			
		||||
        const icon_url = people.small_avatar_url(message);
 | 
			
		||||
        notification_object = new notifications.NotificationAPI(title, {
 | 
			
		||||
            icon: icon_url,
 | 
			
		||||
            body: content,
 | 
			
		||||
            tag: message.id,
 | 
			
		||||
        });
 | 
			
		||||
        notifications.notice_memory.set(key, {
 | 
			
		||||
            obj: notification_object,
 | 
			
		||||
            msg_count,
 | 
			
		||||
            message_id: message.id,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (typeof notification_object.addEventListener === "function") {
 | 
			
		||||
            // Sadly, some third-party Electron apps like Franz/Ferdi
 | 
			
		||||
            // misimplement the Notification API not inheriting from
 | 
			
		||||
            // EventTarget.  This results in addEventListener being
 | 
			
		||||
            // unavailable for them.
 | 
			
		||||
            notification_object.addEventListener("click", () => {
 | 
			
		||||
                notification_object.close();
 | 
			
		||||
                if (message.type !== "test-notification") {
 | 
			
		||||
                    narrow.by_topic(message.id, {trigger: "notification"});
 | 
			
		||||
                }
 | 
			
		||||
                window.focus();
 | 
			
		||||
            });
 | 
			
		||||
            notification_object.addEventListener("close", () => {
 | 
			
		||||
                notifications.notice_memory.delete(key);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function message_is_notifiable(message) {
 | 
			
		||||
    // Independent of the user's notification settings, are there
 | 
			
		||||
    // properties of the message that unconditionally mean we
 | 
			
		||||
    // shouldn't notify about it.
 | 
			
		||||
 | 
			
		||||
    if (message.sent_by_me) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If a message is edited multiple times, we want to err on the side of
 | 
			
		||||
    // not spamming notifications.
 | 
			
		||||
    if (message.notification_sent) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // @-<username> mentions take precedence over muted-ness. Note
 | 
			
		||||
    // that @all mentions are still suppressed by muting.
 | 
			
		||||
    if (message.mentioned_me_directly) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Messages to followed topics take precedence over muted-ness.
 | 
			
		||||
    if (
 | 
			
		||||
        message.type === "stream" &&
 | 
			
		||||
        user_topics.is_topic_followed(message.stream_id, message.topic)
 | 
			
		||||
    ) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Messages to unmuted topics in muted streams may generate desktop notifications.
 | 
			
		||||
    if (
 | 
			
		||||
        message.type === "stream" &&
 | 
			
		||||
        stream_data.is_muted(message.stream_id) &&
 | 
			
		||||
        !user_topics.is_topic_unmuted(message.stream_id, message.topic)
 | 
			
		||||
    ) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (message.type === "stream" && user_topics.is_topic_muted(message.stream_id, message.topic)) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Everything else is on the table; next filter based on notification
 | 
			
		||||
    // settings.
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function should_send_desktop_notification(message) {
 | 
			
		||||
    // Always notify for testing notifications.
 | 
			
		||||
    if (message.type === "test-notification") {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // For streams, send if desktop notifications are enabled for all
 | 
			
		||||
    // message on this stream.
 | 
			
		||||
    if (
 | 
			
		||||
        message.type === "stream" &&
 | 
			
		||||
        stream_data.receives_notifications(message.stream_id, "desktop_notifications")
 | 
			
		||||
    ) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // enable_followed_topic_desktop_notifications determines whether we pop up
 | 
			
		||||
    // a notification for messages in followed topics.
 | 
			
		||||
    if (
 | 
			
		||||
        message.type === "stream" &&
 | 
			
		||||
        user_topics.is_topic_followed(message.stream_id, message.topic) &&
 | 
			
		||||
        user_settings.enable_followed_topic_desktop_notifications
 | 
			
		||||
    ) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // enable_desktop_notifications determines whether we pop up a
 | 
			
		||||
    // notification for direct messages, mentions, and/or alerts.
 | 
			
		||||
    if (!user_settings.enable_desktop_notifications) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // And then we need to check if the message is a direct message,
 | 
			
		||||
    // mention, wildcard mention with wildcard_mentions_notify, or alert.
 | 
			
		||||
    if (message.type === "private") {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (alert_words.notifies(message)) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (message.mentioned_me_directly) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The following blocks for 'wildcard mentions' and 'Followed topic wildcard mentions'
 | 
			
		||||
    // should be placed below (as they are right now) the 'user_settings.enable_desktop_notifications'
 | 
			
		||||
    // block because the global, stream-specific, and followed topic wildcard mention
 | 
			
		||||
    // settings are wrappers around the personal-mention setting.
 | 
			
		||||
    // wildcard mentions
 | 
			
		||||
    if (
 | 
			
		||||
        message.mentioned &&
 | 
			
		||||
        stream_data.receives_notifications(message.stream_id, "wildcard_mentions_notify")
 | 
			
		||||
    ) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Followed topic wildcard mentions
 | 
			
		||||
    if (
 | 
			
		||||
        message.mentioned &&
 | 
			
		||||
        user_topics.is_topic_followed(message.stream_id, message.topic) &&
 | 
			
		||||
        user_settings.enable_followed_topic_wildcard_mentions_notify
 | 
			
		||||
    ) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function should_send_audible_notification(message) {
 | 
			
		||||
    // If `None` is selected as the notification sound, never send
 | 
			
		||||
    // audible notifications regardless of other configuration.
 | 
			
		||||
    if (user_settings.notification_sound === "none") {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // For streams, ding if sounds are enabled for all messages on
 | 
			
		||||
    // this stream.
 | 
			
		||||
    if (
 | 
			
		||||
        message.type === "stream" &&
 | 
			
		||||
        stream_data.receives_notifications(message.stream_id, "audible_notifications")
 | 
			
		||||
    ) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // enable_followed_topic_audible_notifications determines whether we ding
 | 
			
		||||
    // for messages in followed topics.
 | 
			
		||||
    if (
 | 
			
		||||
        message.type === "stream" &&
 | 
			
		||||
        user_topics.is_topic_followed(message.stream_id, message.topic) &&
 | 
			
		||||
        user_settings.enable_followed_topic_audible_notifications
 | 
			
		||||
    ) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // enable_sounds determines whether we ding for direct messages,
 | 
			
		||||
    // mentions, and/or alerts.
 | 
			
		||||
    if (!user_settings.enable_sounds) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // And then we need to check if the message is a direct message,
 | 
			
		||||
    // mention, wildcard mention with wildcard_mentions_notify, or alert.
 | 
			
		||||
    if (message.type === "private" || message.type === "test-notification") {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (alert_words.notifies(message)) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (message.mentioned_me_directly) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The following blocks for 'wildcard mentions' and 'Followed topic wildcard mentions'
 | 
			
		||||
    // should be placed below (as they are right now) the 'user_settings.enable_sounds'
 | 
			
		||||
    // block because the global, stream-specific, and followed topic wildcard mention
 | 
			
		||||
    // settings are wrappers around the personal-mention setting.
 | 
			
		||||
    // wildcard mentions
 | 
			
		||||
    if (
 | 
			
		||||
        message.mentioned &&
 | 
			
		||||
        stream_data.receives_notifications(message.stream_id, "wildcard_mentions_notify")
 | 
			
		||||
    ) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Followed topic wildcard mentions
 | 
			
		||||
    if (
 | 
			
		||||
        message.mentioned &&
 | 
			
		||||
        user_topics.is_topic_followed(message.stream_id, message.topic) &&
 | 
			
		||||
        user_settings.enable_followed_topic_wildcard_mentions_notify
 | 
			
		||||
    ) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function received_messages(messages) {
 | 
			
		||||
    for (const message of messages) {
 | 
			
		||||
        if (!message_is_notifiable(message)) {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        if (!unread.message_unread(message)) {
 | 
			
		||||
            // The message is already read; Zulip is currently in focus.
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        message.notification_sent = true;
 | 
			
		||||
 | 
			
		||||
        if (should_send_desktop_notification(message)) {
 | 
			
		||||
            process_notification({
 | 
			
		||||
                message,
 | 
			
		||||
                desktop_notify: notifications.granted_desktop_notifications_permission(),
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        if (should_send_audible_notification(message)) {
 | 
			
		||||
            ui_util.play_audio($("#user-notification-sound-audio")[0]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function send_test_notification(content) {
 | 
			
		||||
    received_messages([
 | 
			
		||||
        {
 | 
			
		||||
            id: Math.random(),
 | 
			
		||||
            type: "test-notification",
 | 
			
		||||
            sender_email: "notification-bot@zulip.com",
 | 
			
		||||
            sender_full_name: "Notification Bot",
 | 
			
		||||
            display_reply_to: "Notification Bot",
 | 
			
		||||
            content,
 | 
			
		||||
            unread: true,
 | 
			
		||||
        },
 | 
			
		||||
    ]);
 | 
			
		||||
}
 | 
			
		||||
@@ -3,26 +3,21 @@ import $ from "jquery";
 | 
			
		||||
import render_message_sent_banner from "../templates/compose_banner/message_sent_banner.hbs";
 | 
			
		||||
import render_unmute_topic_banner from "../templates/compose_banner/unmute_topic_banner.hbs";
 | 
			
		||||
 | 
			
		||||
import * as alert_words from "./alert_words";
 | 
			
		||||
import * as blueslip from "./blueslip";
 | 
			
		||||
import * as compose_banner from "./compose_banner";
 | 
			
		||||
import * as hash_util from "./hash_util";
 | 
			
		||||
import {$t} from "./i18n";
 | 
			
		||||
import * as message_lists from "./message_lists";
 | 
			
		||||
import * as message_parser from "./message_parser";
 | 
			
		||||
import * as narrow from "./narrow";
 | 
			
		||||
import * as narrow_state from "./narrow_state";
 | 
			
		||||
import * as people from "./people";
 | 
			
		||||
import * as spoilers from "./spoilers";
 | 
			
		||||
import * as stream_data from "./stream_data";
 | 
			
		||||
import * as ui_util from "./ui_util";
 | 
			
		||||
import * as unread from "./unread";
 | 
			
		||||
import {user_settings} from "./user_settings";
 | 
			
		||||
import * as user_topics from "./user_topics";
 | 
			
		||||
 | 
			
		||||
const notice_memory = new Map();
 | 
			
		||||
export const notice_memory = new Map();
 | 
			
		||||
 | 
			
		||||
let NotificationAPI;
 | 
			
		||||
export let NotificationAPI;
 | 
			
		||||
 | 
			
		||||
export function set_notification_api(n) {
 | 
			
		||||
    NotificationAPI = n;
 | 
			
		||||
@@ -140,171 +135,6 @@ export function notify_above_composebox(
 | 
			
		||||
    compose_banner.append_compose_banner_to_banner_list($notification, $("#compose_banners"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function get_notification_content(message) {
 | 
			
		||||
    let content;
 | 
			
		||||
    // Convert the content to plain text, replacing emoji with their alt text
 | 
			
		||||
    const $content = $("<div>").html(message.content);
 | 
			
		||||
    ui_util.replace_emoji_with_text($content);
 | 
			
		||||
    spoilers.hide_spoilers_in_notification($content);
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
        $content.text().trim() === "" &&
 | 
			
		||||
        (message_parser.message_has_image(message) ||
 | 
			
		||||
            message_parser.message_has_attachment(message))
 | 
			
		||||
    ) {
 | 
			
		||||
        content = $t({defaultMessage: "(attached file)"});
 | 
			
		||||
    } else {
 | 
			
		||||
        content = $content.text();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (message.is_me_message) {
 | 
			
		||||
        content = message.sender_full_name + content.slice(3);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
        (message.type === "private" || message.type === "test-notification") &&
 | 
			
		||||
        !user_settings.pm_content_in_desktop_notifications
 | 
			
		||||
    ) {
 | 
			
		||||
        content = "New direct message from " + message.sender_full_name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (content.length > 150) {
 | 
			
		||||
        let i;
 | 
			
		||||
        // Truncate content at a word boundary
 | 
			
		||||
        for (i = 150; i > 0; i -= 1) {
 | 
			
		||||
            if (content[i] === " ") {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        content = content.slice(0, i);
 | 
			
		||||
        content += " [...]";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return content;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function debug_notification_source_value(message) {
 | 
			
		||||
    let notification_source;
 | 
			
		||||
 | 
			
		||||
    if (message.type === "private" || message.type === "test-notification") {
 | 
			
		||||
        notification_source = "pm";
 | 
			
		||||
    } else if (message.mentioned) {
 | 
			
		||||
        notification_source = "mention";
 | 
			
		||||
    } else if (message.alerted) {
 | 
			
		||||
        notification_source = "alert";
 | 
			
		||||
    } else {
 | 
			
		||||
        notification_source = "stream";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    blueslip.debug("Desktop notification from source " + notification_source);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function get_notification_key(message) {
 | 
			
		||||
    let key;
 | 
			
		||||
 | 
			
		||||
    if (message.type === "private" || message.type === "test-notification") {
 | 
			
		||||
        key = message.display_reply_to;
 | 
			
		||||
    } else {
 | 
			
		||||
        const stream_name = stream_data.get_stream_name_from_id(message.stream_id);
 | 
			
		||||
        key = message.sender_full_name + " to " + stream_name + " > " + message.topic;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return key;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function remove_sender_from_list_of_recipients(message) {
 | 
			
		||||
    return `, ${message.display_reply_to}, `
 | 
			
		||||
        .replace(`, ${message.sender_full_name}, `, ", ")
 | 
			
		||||
        .slice(", ".length, -", ".length);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function get_notification_title(message, content, msg_count) {
 | 
			
		||||
    let title = message.sender_full_name;
 | 
			
		||||
    let other_recipients;
 | 
			
		||||
 | 
			
		||||
    if (msg_count > 1) {
 | 
			
		||||
        title = msg_count + " messages from " + title;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    switch (message.type) {
 | 
			
		||||
        case "test-notification":
 | 
			
		||||
            other_recipients = remove_sender_from_list_of_recipients(message);
 | 
			
		||||
            break;
 | 
			
		||||
        case "private":
 | 
			
		||||
            other_recipients = remove_sender_from_list_of_recipients(message);
 | 
			
		||||
            if (message.display_recipient.length > 2) {
 | 
			
		||||
                // If the message has too many recipients to list them all...
 | 
			
		||||
                if (content.length + title.length + other_recipients.length > 230) {
 | 
			
		||||
                    // Then count how many people are in the conversation and summarize
 | 
			
		||||
                    // by saying the conversation is with "you and [number] other people"
 | 
			
		||||
                    other_recipients =
 | 
			
		||||
                        other_recipients.replaceAll(/[^,]/g, "").length + " other people";
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                title += " (to you and " + other_recipients + ")";
 | 
			
		||||
            } else {
 | 
			
		||||
                title += " (to you)";
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        case "stream": {
 | 
			
		||||
            const stream_name = stream_data.get_stream_name_from_id(message.stream_id);
 | 
			
		||||
            title += " (to " + stream_name + " > " + message.topic + ")";
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return title;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function process_notification(notification) {
 | 
			
		||||
    const message = notification.message;
 | 
			
		||||
    const content = get_notification_content(message);
 | 
			
		||||
    const key = get_notification_key(message);
 | 
			
		||||
    let notification_object;
 | 
			
		||||
    let msg_count = 1;
 | 
			
		||||
 | 
			
		||||
    debug_notification_source_value(message);
 | 
			
		||||
 | 
			
		||||
    if (notice_memory.has(key)) {
 | 
			
		||||
        msg_count = notice_memory.get(key).msg_count + 1;
 | 
			
		||||
        notification_object = notice_memory.get(key).obj;
 | 
			
		||||
        notification_object.close();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const title = get_notification_title(message, content, msg_count);
 | 
			
		||||
 | 
			
		||||
    if (notification.desktop_notify) {
 | 
			
		||||
        const icon_url = people.small_avatar_url(message);
 | 
			
		||||
        notification_object = new NotificationAPI(title, {
 | 
			
		||||
            icon: icon_url,
 | 
			
		||||
            body: content,
 | 
			
		||||
            tag: message.id,
 | 
			
		||||
        });
 | 
			
		||||
        notice_memory.set(key, {
 | 
			
		||||
            obj: notification_object,
 | 
			
		||||
            msg_count,
 | 
			
		||||
            message_id: message.id,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (typeof notification_object.addEventListener === "function") {
 | 
			
		||||
            // Sadly, some third-party Electron apps like Franz/Ferdi
 | 
			
		||||
            // misimplement the Notification API not inheriting from
 | 
			
		||||
            // EventTarget.  This results in addEventListener being
 | 
			
		||||
            // unavailable for them.
 | 
			
		||||
            notification_object.addEventListener("click", () => {
 | 
			
		||||
                notification_object.close();
 | 
			
		||||
                if (message.type !== "test-notification") {
 | 
			
		||||
                    narrow.by_topic(message.id, {trigger: "notification"});
 | 
			
		||||
                }
 | 
			
		||||
                window.focus();
 | 
			
		||||
            });
 | 
			
		||||
            notification_object.addEventListener("close", () => {
 | 
			
		||||
                notice_memory.delete(key);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function close_notification(message) {
 | 
			
		||||
    for (const [key, notice_mem_entry] of notice_memory) {
 | 
			
		||||
        if (notice_mem_entry.message_id === message.id) {
 | 
			
		||||
@@ -314,192 +144,6 @@ export function close_notification(message) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function message_is_notifiable(message) {
 | 
			
		||||
    // Independent of the user's notification settings, are there
 | 
			
		||||
    // properties of the message that unconditionally mean we
 | 
			
		||||
    // shouldn't notify about it.
 | 
			
		||||
 | 
			
		||||
    if (message.sent_by_me) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If a message is edited multiple times, we want to err on the side of
 | 
			
		||||
    // not spamming notifications.
 | 
			
		||||
    if (message.notification_sent) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // @-<username> mentions take precedence over muted-ness. Note
 | 
			
		||||
    // that @all mentions are still suppressed by muting.
 | 
			
		||||
    if (message.mentioned_me_directly) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Messages to followed topics take precedence over muted-ness.
 | 
			
		||||
    if (
 | 
			
		||||
        message.type === "stream" &&
 | 
			
		||||
        user_topics.is_topic_followed(message.stream_id, message.topic)
 | 
			
		||||
    ) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Messages to unmuted topics in muted streams may generate desktop notifications.
 | 
			
		||||
    if (
 | 
			
		||||
        message.type === "stream" &&
 | 
			
		||||
        stream_data.is_muted(message.stream_id) &&
 | 
			
		||||
        !user_topics.is_topic_unmuted(message.stream_id, message.topic)
 | 
			
		||||
    ) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (message.type === "stream" && user_topics.is_topic_muted(message.stream_id, message.topic)) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Everything else is on the table; next filter based on notification
 | 
			
		||||
    // settings.
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function should_send_desktop_notification(message) {
 | 
			
		||||
    // Always notify for testing notifications.
 | 
			
		||||
    if (message.type === "test-notification") {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // For streams, send if desktop notifications are enabled for all
 | 
			
		||||
    // message on this stream.
 | 
			
		||||
    if (
 | 
			
		||||
        message.type === "stream" &&
 | 
			
		||||
        stream_data.receives_notifications(message.stream_id, "desktop_notifications")
 | 
			
		||||
    ) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // enable_followed_topic_desktop_notifications determines whether we pop up
 | 
			
		||||
    // a notification for messages in followed topics.
 | 
			
		||||
    if (
 | 
			
		||||
        message.type === "stream" &&
 | 
			
		||||
        user_topics.is_topic_followed(message.stream_id, message.topic) &&
 | 
			
		||||
        user_settings.enable_followed_topic_desktop_notifications
 | 
			
		||||
    ) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // enable_desktop_notifications determines whether we pop up a
 | 
			
		||||
    // notification for direct messages, mentions, and/or alerts.
 | 
			
		||||
    if (!user_settings.enable_desktop_notifications) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // And then we need to check if the message is a direct message,
 | 
			
		||||
    // mention, wildcard mention with wildcard_mentions_notify, or alert.
 | 
			
		||||
    if (message.type === "private") {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (alert_words.notifies(message)) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (message.mentioned_me_directly) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The following blocks for 'wildcard mentions' and 'Followed topic wildcard mentions'
 | 
			
		||||
    // should be placed below (as they are right now) the 'user_settings.enable_desktop_notifications'
 | 
			
		||||
    // block because the global, stream-specific, and followed topic wildcard mention
 | 
			
		||||
    // settings are wrappers around the personal-mention setting.
 | 
			
		||||
    // wildcard mentions
 | 
			
		||||
    if (
 | 
			
		||||
        message.mentioned &&
 | 
			
		||||
        stream_data.receives_notifications(message.stream_id, "wildcard_mentions_notify")
 | 
			
		||||
    ) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Followed topic wildcard mentions
 | 
			
		||||
    if (
 | 
			
		||||
        message.mentioned &&
 | 
			
		||||
        user_topics.is_topic_followed(message.stream_id, message.topic) &&
 | 
			
		||||
        user_settings.enable_followed_topic_wildcard_mentions_notify
 | 
			
		||||
    ) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function should_send_audible_notification(message) {
 | 
			
		||||
    // If `None` is selected as the notification sound, never send
 | 
			
		||||
    // audible notifications regardless of other configuration.
 | 
			
		||||
    if (user_settings.notification_sound === "none") {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // For streams, ding if sounds are enabled for all messages on
 | 
			
		||||
    // this stream.
 | 
			
		||||
    if (
 | 
			
		||||
        message.type === "stream" &&
 | 
			
		||||
        stream_data.receives_notifications(message.stream_id, "audible_notifications")
 | 
			
		||||
    ) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // enable_followed_topic_audible_notifications determines whether we ding
 | 
			
		||||
    // for messages in followed topics.
 | 
			
		||||
    if (
 | 
			
		||||
        message.type === "stream" &&
 | 
			
		||||
        user_topics.is_topic_followed(message.stream_id, message.topic) &&
 | 
			
		||||
        user_settings.enable_followed_topic_audible_notifications
 | 
			
		||||
    ) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // enable_sounds determines whether we ding for direct messages,
 | 
			
		||||
    // mentions, and/or alerts.
 | 
			
		||||
    if (!user_settings.enable_sounds) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // And then we need to check if the message is a direct message,
 | 
			
		||||
    // mention, wildcard mention with wildcard_mentions_notify, or alert.
 | 
			
		||||
    if (message.type === "private" || message.type === "test-notification") {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (alert_words.notifies(message)) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (message.mentioned_me_directly) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The following blocks for 'wildcard mentions' and 'Followed topic wildcard mentions'
 | 
			
		||||
    // should be placed below (as they are right now) the 'user_settings.enable_sounds'
 | 
			
		||||
    // block because the global, stream-specific, and followed topic wildcard mention
 | 
			
		||||
    // settings are wrappers around the personal-mention setting.
 | 
			
		||||
    // wildcard mentions
 | 
			
		||||
    if (
 | 
			
		||||
        message.mentioned &&
 | 
			
		||||
        stream_data.receives_notifications(message.stream_id, "wildcard_mentions_notify")
 | 
			
		||||
    ) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Followed topic wildcard mentions
 | 
			
		||||
    if (
 | 
			
		||||
        message.mentioned &&
 | 
			
		||||
        user_topics.is_topic_followed(message.stream_id, message.topic) &&
 | 
			
		||||
        user_settings.enable_followed_topic_wildcard_mentions_notify
 | 
			
		||||
    ) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function granted_desktop_notifications_permission() {
 | 
			
		||||
    return NotificationAPI && NotificationAPI.permission === "granted";
 | 
			
		||||
}
 | 
			
		||||
@@ -510,44 +154,6 @@ export function request_desktop_notifications_permission() {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function received_messages(messages) {
 | 
			
		||||
    for (const message of messages) {
 | 
			
		||||
        if (!message_is_notifiable(message)) {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        if (!unread.message_unread(message)) {
 | 
			
		||||
            // The message is already read; Zulip is currently in focus.
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        message.notification_sent = true;
 | 
			
		||||
 | 
			
		||||
        if (should_send_desktop_notification(message)) {
 | 
			
		||||
            process_notification({
 | 
			
		||||
                message,
 | 
			
		||||
                desktop_notify: granted_desktop_notifications_permission(),
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        if (should_send_audible_notification(message)) {
 | 
			
		||||
            ui_util.play_audio($("#user-notification-sound-audio")[0]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function send_test_notification(content) {
 | 
			
		||||
    received_messages([
 | 
			
		||||
        {
 | 
			
		||||
            id: Math.random(),
 | 
			
		||||
            type: "test-notification",
 | 
			
		||||
            sender_email: "notification-bot@zulip.com",
 | 
			
		||||
            sender_full_name: "Notification Bot",
 | 
			
		||||
            display_reply_to: "Notification Bot",
 | 
			
		||||
            content,
 | 
			
		||||
            unread: true,
 | 
			
		||||
        },
 | 
			
		||||
    ]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Note that this returns values that are not HTML-escaped, for use in
 | 
			
		||||
// Handlebars templates that will do further escaping.
 | 
			
		||||
function get_message_header(message) {
 | 
			
		||||
@@ -736,13 +342,13 @@ export function reify_message_id(opts) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function register_click_handlers({on_click_scroll_to_selected}) {
 | 
			
		||||
export function register_click_handlers({on_click_scroll_to_selected}) {
 | 
			
		||||
    $("#compose_banners").on(
 | 
			
		||||
        "click",
 | 
			
		||||
        ".narrow_to_recipient .above_compose_banner_action_link",
 | 
			
		||||
        (e) => {
 | 
			
		||||
            const message_id = $(e.currentTarget).data("message-id");
 | 
			
		||||
            narrow.by_topic(message_id, {trigger: "compose_notification"});
 | 
			
		||||
            narrow.by_topic(message_id, {trigger: "notification"});
 | 
			
		||||
            e.stopPropagation();
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
        },
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ import * as blueslip from "./blueslip";
 | 
			
		||||
import * as channel from "./channel";
 | 
			
		||||
import * as confirm_dialog from "./confirm_dialog";
 | 
			
		||||
import {$t, $t_html} from "./i18n";
 | 
			
		||||
import * as notifications from "./notifications";
 | 
			
		||||
import * as message_notifications from "./message_notifications";
 | 
			
		||||
import {page_params} from "./page_params";
 | 
			
		||||
import * as settings_config from "./settings_config";
 | 
			
		||||
import * as settings_org from "./settings_org";
 | 
			
		||||
@@ -280,7 +280,7 @@ export function set_up(settings_panel) {
 | 
			
		||||
    // intentionally don't let organization administrators set
 | 
			
		||||
    // organization-level defaults.
 | 
			
		||||
    $container.find(".send_test_notification").on("click", () => {
 | 
			
		||||
        notifications.send_test_notification(
 | 
			
		||||
        message_notifications.send_test_notification(
 | 
			
		||||
            $t({defaultMessage: "This is what a Zulip notification looks like."}),
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -22,8 +22,8 @@ const {run_test} = require("./lib/test");
 | 
			
		||||
// replace them with {}.
 | 
			
		||||
const huddle_data = mock_esm("../src/huddle_data");
 | 
			
		||||
const message_lists = mock_esm("../src/message_lists");
 | 
			
		||||
const message_notifications = mock_esm("../src/message_notifications");
 | 
			
		||||
const message_util = mock_esm("../src/message_util");
 | 
			
		||||
const notifications = mock_esm("../src/notifications");
 | 
			
		||||
const pm_list = mock_esm("../src/pm_list");
 | 
			
		||||
const recent_view_data = mock_esm("../src/recent_view_data");
 | 
			
		||||
const stream_list = mock_esm("../src/stream_list");
 | 
			
		||||
@@ -99,9 +99,9 @@ run_test("insert_message", ({override}) => {
 | 
			
		||||
    assert.equal(message_store.get(new_message.id), undefined);
 | 
			
		||||
 | 
			
		||||
    helper.redirect(huddle_data, "process_loaded_messages");
 | 
			
		||||
    helper.redirect(message_notifications, "received_messages");
 | 
			
		||||
    helper.redirect(message_util, "add_new_messages_data");
 | 
			
		||||
    helper.redirect(message_util, "add_new_messages");
 | 
			
		||||
    helper.redirect(notifications, "received_messages");
 | 
			
		||||
    helper.redirect(recent_view_data, "process_message");
 | 
			
		||||
    helper.redirect(stream_list, "update_streams_sidebar");
 | 
			
		||||
    helper.redirect(unread_ops, "process_visible");
 | 
			
		||||
@@ -122,7 +122,7 @@ run_test("insert_message", ({override}) => {
 | 
			
		||||
        [message_util, "add_new_messages"],
 | 
			
		||||
        [unread_ui, "update_unread_counts"],
 | 
			
		||||
        [unread_ops, "process_visible"],
 | 
			
		||||
        [notifications, "received_messages"],
 | 
			
		||||
        [message_notifications, "received_messages"],
 | 
			
		||||
        [stream_list, "update_streams_sidebar"],
 | 
			
		||||
        [recent_view_data, "process_message"],
 | 
			
		||||
    ]);
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ const {page_params} = require("./lib/zpage_params");
 | 
			
		||||
 | 
			
		||||
const message_edit = mock_esm("../src/message_edit");
 | 
			
		||||
const message_lists = mock_esm("../src/message_lists");
 | 
			
		||||
const notifications = mock_esm("../src/notifications");
 | 
			
		||||
const message_notifications = mock_esm("../src/message_notifications");
 | 
			
		||||
const pm_list = mock_esm("../src/pm_list");
 | 
			
		||||
const stream_list = mock_esm("../src/stream_list");
 | 
			
		||||
const unread_ui = mock_esm("../src/unread_ui");
 | 
			
		||||
@@ -99,7 +99,7 @@ run_test("update_messages", () => {
 | 
			
		||||
 | 
			
		||||
    const side_effects = [
 | 
			
		||||
        [message_edit, "end_message_edit"],
 | 
			
		||||
        [notifications, "received_messages"],
 | 
			
		||||
        [message_notifications, "received_messages"],
 | 
			
		||||
        [unread_ui, "update_unread_counts"],
 | 
			
		||||
        [stream_list, "update_streams_sidebar"],
 | 
			
		||||
        [pm_list, "update_private_messages"],
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ const user_topics = zrequire("user_topics");
 | 
			
		||||
const stream_data = zrequire("stream_data");
 | 
			
		||||
 | 
			
		||||
const notifications = zrequire("notifications");
 | 
			
		||||
const message_notifications = zrequire("message_notifications");
 | 
			
		||||
 | 
			
		||||
// Not muted streams
 | 
			
		||||
const general = {
 | 
			
		||||
@@ -82,10 +83,10 @@ test("message_is_notifiable", () => {
 | 
			
		||||
        stream_id: general.stream_id,
 | 
			
		||||
        topic: "whatever",
 | 
			
		||||
    };
 | 
			
		||||
    assert.equal(notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    // Not notifiable because it was sent by the current user
 | 
			
		||||
    assert.equal(notifications.message_is_notifiable(message), false);
 | 
			
		||||
    assert.equal(message_notifications.message_is_notifiable(message), false);
 | 
			
		||||
 | 
			
		||||
    // Case 2: If the user has already been sent a notification about this message,
 | 
			
		||||
    //  DO NOT notify the user
 | 
			
		||||
@@ -103,9 +104,9 @@ test("message_is_notifiable", () => {
 | 
			
		||||
        stream_id: general.stream_id,
 | 
			
		||||
        topic: "whatever",
 | 
			
		||||
    };
 | 
			
		||||
    assert.equal(notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.message_is_notifiable(message), false);
 | 
			
		||||
    assert.equal(message_notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.message_is_notifiable(message), false);
 | 
			
		||||
 | 
			
		||||
    // Case 3: If a message mentions the user directly,
 | 
			
		||||
    //  DO notify the user
 | 
			
		||||
@@ -121,9 +122,9 @@ test("message_is_notifiable", () => {
 | 
			
		||||
        stream_id: muted.stream_id,
 | 
			
		||||
        topic: "topic_three",
 | 
			
		||||
    };
 | 
			
		||||
    assert.equal(notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.message_is_notifiable(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.message_is_notifiable(message), true);
 | 
			
		||||
 | 
			
		||||
    // Case 4: If the message has been sent to a followed topic,
 | 
			
		||||
    // DO visually and audibly notify the user if 'enable_followed_topic_desktop_notifications'
 | 
			
		||||
@@ -140,17 +141,17 @@ test("message_is_notifiable", () => {
 | 
			
		||||
        stream_id: general.stream_id,
 | 
			
		||||
        topic: "followed topic",
 | 
			
		||||
    };
 | 
			
		||||
    assert.equal(notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.message_is_notifiable(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.message_is_notifiable(message), true);
 | 
			
		||||
 | 
			
		||||
    // But not if 'enable_followed_topic_desktop_notifications'
 | 
			
		||||
    // and 'enable_followed_topic_audible_notifications' are disabled.
 | 
			
		||||
    user_settings.enable_followed_topic_desktop_notifications = false;
 | 
			
		||||
    user_settings.enable_followed_topic_audible_notifications = false;
 | 
			
		||||
    assert.equal(notifications.should_send_desktop_notification(message), false);
 | 
			
		||||
    assert.equal(notifications.should_send_audible_notification(message), false);
 | 
			
		||||
    assert.equal(notifications.message_is_notifiable(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_desktop_notification(message), false);
 | 
			
		||||
    assert.equal(message_notifications.should_send_audible_notification(message), false);
 | 
			
		||||
    assert.equal(message_notifications.message_is_notifiable(message), true);
 | 
			
		||||
 | 
			
		||||
    // Reset state
 | 
			
		||||
    user_settings.enable_followed_topic_desktop_notifications = true;
 | 
			
		||||
@@ -168,9 +169,9 @@ test("message_is_notifiable", () => {
 | 
			
		||||
        stream_id: general.stream_id,
 | 
			
		||||
        topic: "vanilla",
 | 
			
		||||
    };
 | 
			
		||||
    assert.equal(notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.message_is_notifiable(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.message_is_notifiable(message), true);
 | 
			
		||||
 | 
			
		||||
    // Case 6:
 | 
			
		||||
    // Wildcard mention should trigger notification in unmuted topic
 | 
			
		||||
@@ -186,21 +187,21 @@ test("message_is_notifiable", () => {
 | 
			
		||||
        stream_id: general.stream_id,
 | 
			
		||||
        topic: "vanilla",
 | 
			
		||||
    };
 | 
			
		||||
    assert.equal(notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.message_is_notifiable(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.message_is_notifiable(message), true);
 | 
			
		||||
 | 
			
		||||
    // But not if it's disabled
 | 
			
		||||
    user_settings.wildcard_mentions_notify = false;
 | 
			
		||||
    assert.equal(notifications.should_send_desktop_notification(message), false);
 | 
			
		||||
    assert.equal(notifications.should_send_audible_notification(message), false);
 | 
			
		||||
    assert.equal(notifications.message_is_notifiable(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_desktop_notification(message), false);
 | 
			
		||||
    assert.equal(message_notifications.should_send_audible_notification(message), false);
 | 
			
		||||
    assert.equal(message_notifications.message_is_notifiable(message), true);
 | 
			
		||||
 | 
			
		||||
    // And the stream-level setting overrides the global setting
 | 
			
		||||
    general.wildcard_mentions_notify = true;
 | 
			
		||||
    assert.equal(notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.message_is_notifiable(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.message_is_notifiable(message), true);
 | 
			
		||||
 | 
			
		||||
    // Reset state
 | 
			
		||||
    user_settings.wildcard_mentions_notify = true;
 | 
			
		||||
@@ -220,9 +221,9 @@ test("message_is_notifiable", () => {
 | 
			
		||||
        stream_id: muted.stream_id,
 | 
			
		||||
        topic: "whatever",
 | 
			
		||||
    };
 | 
			
		||||
    assert.equal(notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.message_is_notifiable(message), false);
 | 
			
		||||
    assert.equal(message_notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.message_is_notifiable(message), false);
 | 
			
		||||
 | 
			
		||||
    // Case 8: If a message is in a muted stream
 | 
			
		||||
    //  and does mention the user DIRECTLY,
 | 
			
		||||
@@ -238,9 +239,9 @@ test("message_is_notifiable", () => {
 | 
			
		||||
        stream_id: muted.stream_id,
 | 
			
		||||
        topic: "whatever",
 | 
			
		||||
    };
 | 
			
		||||
    assert.equal(notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.message_is_notifiable(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.message_is_notifiable(message), true);
 | 
			
		||||
 | 
			
		||||
    // Case 9: If a message is in a muted topic
 | 
			
		||||
    //  and does not mention the user DIRECTLY (i.e. wildcard mention),
 | 
			
		||||
@@ -256,9 +257,9 @@ test("message_is_notifiable", () => {
 | 
			
		||||
        stream_id: general.stream_id,
 | 
			
		||||
        topic: "muted topic",
 | 
			
		||||
    };
 | 
			
		||||
    assert.equal(notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.message_is_notifiable(message), false);
 | 
			
		||||
    assert.equal(message_notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.message_is_notifiable(message), false);
 | 
			
		||||
 | 
			
		||||
    // Case 10:
 | 
			
		||||
    // Wildcard mentions in a followed topic with 'wildcard_mentions_notify',
 | 
			
		||||
@@ -280,15 +281,15 @@ test("message_is_notifiable", () => {
 | 
			
		||||
        stream_id: general.stream_id,
 | 
			
		||||
        topic: "followed topic",
 | 
			
		||||
    };
 | 
			
		||||
    assert.equal(notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.message_is_notifiable(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_audible_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.message_is_notifiable(message), true);
 | 
			
		||||
 | 
			
		||||
    // But not if 'enable_followed_topic_wildcard_mentions_notify' is disabled
 | 
			
		||||
    user_settings.enable_followed_topic_wildcard_mentions_notify = false;
 | 
			
		||||
    assert.equal(notifications.should_send_desktop_notification(message), false);
 | 
			
		||||
    assert.equal(notifications.should_send_audible_notification(message), false);
 | 
			
		||||
    assert.equal(notifications.message_is_notifiable(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_desktop_notification(message), false);
 | 
			
		||||
    assert.equal(message_notifications.should_send_audible_notification(message), false);
 | 
			
		||||
    assert.equal(message_notifications.message_is_notifiable(message), true);
 | 
			
		||||
 | 
			
		||||
    // Reset state
 | 
			
		||||
    user_settings.wildcard_mentions_notify = true;
 | 
			
		||||
@@ -310,9 +311,9 @@ test("message_is_notifiable", () => {
 | 
			
		||||
        topic: "whatever",
 | 
			
		||||
    };
 | 
			
		||||
    user_settings.notification_sound = "none";
 | 
			
		||||
    assert.equal(notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(notifications.should_send_audible_notification(message), false);
 | 
			
		||||
    assert.equal(notifications.message_is_notifiable(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_desktop_notification(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_audible_notification(message), false);
 | 
			
		||||
    assert.equal(message_notifications.message_is_notifiable(message), true);
 | 
			
		||||
 | 
			
		||||
    // Reset state
 | 
			
		||||
    user_settings.notification_sound = "ding";
 | 
			
		||||
@@ -332,9 +333,9 @@ test("message_is_notifiable", () => {
 | 
			
		||||
        stream_id: general.stream_id,
 | 
			
		||||
        topic: "whatever",
 | 
			
		||||
    };
 | 
			
		||||
    assert.equal(notifications.should_send_desktop_notification(message), false);
 | 
			
		||||
    assert.equal(notifications.should_send_audible_notification(message), false);
 | 
			
		||||
    assert.equal(notifications.message_is_notifiable(message), true);
 | 
			
		||||
    assert.equal(message_notifications.should_send_desktop_notification(message), false);
 | 
			
		||||
    assert.equal(message_notifications.should_send_audible_notification(message), false);
 | 
			
		||||
    assert.equal(message_notifications.message_is_notifiable(message), true);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test("basic_notifications", () => {
 | 
			
		||||
@@ -393,7 +394,7 @@ test("basic_notifications", () => {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Send notification.
 | 
			
		||||
    notifications.process_notification({message: message_1, desktop_notify: true});
 | 
			
		||||
    message_notifications.process_notification({message: message_1, desktop_notify: true});
 | 
			
		||||
    n = notifications.get_notifications();
 | 
			
		||||
    assert.equal(n.has("Jesse Pinkman to general > whatever"), true);
 | 
			
		||||
    assert.equal(n.size, 1);
 | 
			
		||||
@@ -408,7 +409,7 @@ test("basic_notifications", () => {
 | 
			
		||||
 | 
			
		||||
    // Send notification.
 | 
			
		||||
    message_1.id = 1001;
 | 
			
		||||
    notifications.process_notification({message: message_1, desktop_notify: true});
 | 
			
		||||
    message_notifications.process_notification({message: message_1, desktop_notify: true});
 | 
			
		||||
    n = notifications.get_notifications();
 | 
			
		||||
    assert.equal(n.has("Jesse Pinkman to general > whatever"), true);
 | 
			
		||||
    assert.equal(n.size, 1);
 | 
			
		||||
@@ -416,14 +417,14 @@ test("basic_notifications", () => {
 | 
			
		||||
 | 
			
		||||
    // Process same message again. Notification count shouldn't increase.
 | 
			
		||||
    message_1.id = 1002;
 | 
			
		||||
    notifications.process_notification({message: message_1, desktop_notify: true});
 | 
			
		||||
    message_notifications.process_notification({message: message_1, desktop_notify: true});
 | 
			
		||||
    n = notifications.get_notifications();
 | 
			
		||||
    assert.equal(n.has("Jesse Pinkman to general > whatever"), true);
 | 
			
		||||
    assert.equal(n.size, 1);
 | 
			
		||||
    assert.equal(last_shown_message_id, message_1.id);
 | 
			
		||||
 | 
			
		||||
    // Send another message. Notification count should increase.
 | 
			
		||||
    notifications.process_notification({message: message_2, desktop_notify: true});
 | 
			
		||||
    message_notifications.process_notification({message: message_2, desktop_notify: true});
 | 
			
		||||
    n = notifications.get_notifications();
 | 
			
		||||
    assert.equal(n.has("Gus Fring to general > lunch"), true);
 | 
			
		||||
    assert.equal(n.has("Jesse Pinkman to general > whatever"), true);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user