notifications: Split out message_notifications module.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg
2023-10-06 13:29:25 -07:00
committed by Tim Abbott
parent e48771993b
commit db20fd12e0
8 changed files with 472 additions and 459 deletions

View File

@@ -133,6 +133,7 @@ EXEMPT_FILES = make_set(
"web/src/message_list_view.js", "web/src/message_list_view.js",
"web/src/message_lists.js", "web/src/message_lists.js",
"web/src/message_live_update.js", "web/src/message_live_update.js",
"web/src/message_notifications.js",
"web/src/message_scroll.js", "web/src/message_scroll.js",
"web/src/message_scroll_state.ts", "web/src/message_scroll_state.ts",
"web/src/message_util.js", "web/src/message_util.js",

View File

@@ -13,6 +13,7 @@ import * as message_edit from "./message_edit";
import * as message_edit_history from "./message_edit_history"; import * as message_edit_history from "./message_edit_history";
import * as message_helper from "./message_helper"; import * as message_helper from "./message_helper";
import * as message_lists from "./message_lists"; import * as message_lists from "./message_lists";
import * as message_notifications from "./message_notifications";
import * as message_store from "./message_store"; import * as message_store from "./message_store";
import * as message_util from "./message_util"; import * as message_util from "./message_util";
import * as narrow from "./narrow"; import * as narrow from "./narrow";
@@ -161,7 +162,7 @@ export function insert_new_messages(messages, sent_by_this_client) {
} }
unread_ops.process_visible(); unread_ops.process_visible();
notifications.received_messages(messages); message_notifications.received_messages(messages);
stream_list.update_streams_sidebar(); stream_list.update_streams_sidebar();
pm_list.update_private_messages(); pm_list.update_private_messages();
recent_view_ui.process_messages(messages); recent_view_ui.process_messages(messages);
@@ -482,7 +483,7 @@ export function update_messages(events) {
anchor_message.last_edit_timestamp = event.edit_timestamp; 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); alert_words.process_message(anchor_message);
} }

View 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,
},
]);
}

View File

@@ -3,26 +3,21 @@ import $ from "jquery";
import render_message_sent_banner from "../templates/compose_banner/message_sent_banner.hbs"; 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 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 blueslip from "./blueslip";
import * as compose_banner from "./compose_banner"; import * as compose_banner from "./compose_banner";
import * as hash_util from "./hash_util"; import * as hash_util from "./hash_util";
import {$t} from "./i18n"; import {$t} from "./i18n";
import * as message_lists from "./message_lists"; import * as message_lists from "./message_lists";
import * as message_parser from "./message_parser";
import * as narrow from "./narrow"; import * as narrow from "./narrow";
import * as narrow_state from "./narrow_state"; import * as narrow_state from "./narrow_state";
import * as people from "./people"; import * as people from "./people";
import * as spoilers from "./spoilers";
import * as stream_data from "./stream_data"; 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 {user_settings} from "./user_settings";
import * as user_topics from "./user_topics"; 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) { export function set_notification_api(n) {
NotificationAPI = n; NotificationAPI = n;
@@ -140,171 +135,6 @@ export function notify_above_composebox(
compose_banner.append_compose_banner_to_banner_list($notification, $("#compose_banners")); 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) { export function close_notification(message) {
for (const [key, notice_mem_entry] of notice_memory) { for (const [key, notice_mem_entry] of notice_memory) {
if (notice_mem_entry.message_id === message.id) { 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() { export function granted_desktop_notifications_permission() {
return NotificationAPI && NotificationAPI.permission === "granted"; 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 // Note that this returns values that are not HTML-escaped, for use in
// Handlebars templates that will do further escaping. // Handlebars templates that will do further escaping.
function get_message_header(message) { 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( $("#compose_banners").on(
"click", "click",
".narrow_to_recipient .above_compose_banner_action_link", ".narrow_to_recipient .above_compose_banner_action_link",
(e) => { (e) => {
const message_id = $(e.currentTarget).data("message-id"); 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.stopPropagation();
e.preventDefault(); e.preventDefault();
}, },

View File

@@ -7,7 +7,7 @@ import * as blueslip from "./blueslip";
import * as channel from "./channel"; import * as channel from "./channel";
import * as confirm_dialog from "./confirm_dialog"; import * as confirm_dialog from "./confirm_dialog";
import {$t, $t_html} from "./i18n"; 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 {page_params} from "./page_params";
import * as settings_config from "./settings_config"; import * as settings_config from "./settings_config";
import * as settings_org from "./settings_org"; import * as settings_org from "./settings_org";
@@ -280,7 +280,7 @@ export function set_up(settings_panel) {
// intentionally don't let organization administrators set // intentionally don't let organization administrators set
// organization-level defaults. // organization-level defaults.
$container.find(".send_test_notification").on("click", () => { $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."}), $t({defaultMessage: "This is what a Zulip notification looks like."}),
); );
}); });

View File

@@ -22,8 +22,8 @@ const {run_test} = require("./lib/test");
// replace them with {}. // replace them with {}.
const huddle_data = mock_esm("../src/huddle_data"); const huddle_data = mock_esm("../src/huddle_data");
const message_lists = mock_esm("../src/message_lists"); 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 message_util = mock_esm("../src/message_util");
const notifications = mock_esm("../src/notifications");
const pm_list = mock_esm("../src/pm_list"); const pm_list = mock_esm("../src/pm_list");
const recent_view_data = mock_esm("../src/recent_view_data"); const recent_view_data = mock_esm("../src/recent_view_data");
const stream_list = mock_esm("../src/stream_list"); 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); assert.equal(message_store.get(new_message.id), undefined);
helper.redirect(huddle_data, "process_loaded_messages"); 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_data");
helper.redirect(message_util, "add_new_messages"); helper.redirect(message_util, "add_new_messages");
helper.redirect(notifications, "received_messages");
helper.redirect(recent_view_data, "process_message"); helper.redirect(recent_view_data, "process_message");
helper.redirect(stream_list, "update_streams_sidebar"); helper.redirect(stream_list, "update_streams_sidebar");
helper.redirect(unread_ops, "process_visible"); helper.redirect(unread_ops, "process_visible");
@@ -122,7 +122,7 @@ run_test("insert_message", ({override}) => {
[message_util, "add_new_messages"], [message_util, "add_new_messages"],
[unread_ui, "update_unread_counts"], [unread_ui, "update_unread_counts"],
[unread_ops, "process_visible"], [unread_ops, "process_visible"],
[notifications, "received_messages"], [message_notifications, "received_messages"],
[stream_list, "update_streams_sidebar"], [stream_list, "update_streams_sidebar"],
[recent_view_data, "process_message"], [recent_view_data, "process_message"],
]); ]);

View File

@@ -9,7 +9,7 @@ const {page_params} = require("./lib/zpage_params");
const message_edit = mock_esm("../src/message_edit"); const message_edit = mock_esm("../src/message_edit");
const message_lists = mock_esm("../src/message_lists"); 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 pm_list = mock_esm("../src/pm_list");
const stream_list = mock_esm("../src/stream_list"); const stream_list = mock_esm("../src/stream_list");
const unread_ui = mock_esm("../src/unread_ui"); const unread_ui = mock_esm("../src/unread_ui");
@@ -99,7 +99,7 @@ run_test("update_messages", () => {
const side_effects = [ const side_effects = [
[message_edit, "end_message_edit"], [message_edit, "end_message_edit"],
[notifications, "received_messages"], [message_notifications, "received_messages"],
[unread_ui, "update_unread_counts"], [unread_ui, "update_unread_counts"],
[stream_list, "update_streams_sidebar"], [stream_list, "update_streams_sidebar"],
[pm_list, "update_private_messages"], [pm_list, "update_private_messages"],

View File

@@ -13,6 +13,7 @@ const user_topics = zrequire("user_topics");
const stream_data = zrequire("stream_data"); const stream_data = zrequire("stream_data");
const notifications = zrequire("notifications"); const notifications = zrequire("notifications");
const message_notifications = zrequire("message_notifications");
// Not muted streams // Not muted streams
const general = { const general = {
@@ -82,10 +83,10 @@ test("message_is_notifiable", () => {
stream_id: general.stream_id, stream_id: general.stream_id,
topic: "whatever", topic: "whatever",
}; };
assert.equal(notifications.should_send_desktop_notification(message), true); assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true); assert.equal(message_notifications.should_send_audible_notification(message), true);
// Not notifiable because it was sent by the current user // 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, // Case 2: If the user has already been sent a notification about this message,
// DO NOT notify the user // DO NOT notify the user
@@ -103,9 +104,9 @@ test("message_is_notifiable", () => {
stream_id: general.stream_id, stream_id: general.stream_id,
topic: "whatever", topic: "whatever",
}; };
assert.equal(notifications.should_send_desktop_notification(message), true); assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true); assert.equal(message_notifications.should_send_audible_notification(message), true);
assert.equal(notifications.message_is_notifiable(message), false); assert.equal(message_notifications.message_is_notifiable(message), false);
// Case 3: If a message mentions the user directly, // Case 3: If a message mentions the user directly,
// DO notify the user // DO notify the user
@@ -121,9 +122,9 @@ test("message_is_notifiable", () => {
stream_id: muted.stream_id, stream_id: muted.stream_id,
topic: "topic_three", topic: "topic_three",
}; };
assert.equal(notifications.should_send_desktop_notification(message), true); assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true); assert.equal(message_notifications.should_send_audible_notification(message), true);
assert.equal(notifications.message_is_notifiable(message), true); assert.equal(message_notifications.message_is_notifiable(message), true);
// Case 4: If the message has been sent to a followed topic, // 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' // 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, stream_id: general.stream_id,
topic: "followed topic", topic: "followed topic",
}; };
assert.equal(notifications.should_send_desktop_notification(message), true); assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true); assert.equal(message_notifications.should_send_audible_notification(message), true);
assert.equal(notifications.message_is_notifiable(message), true); assert.equal(message_notifications.message_is_notifiable(message), true);
// But not if 'enable_followed_topic_desktop_notifications' // But not if 'enable_followed_topic_desktop_notifications'
// and 'enable_followed_topic_audible_notifications' are disabled. // and 'enable_followed_topic_audible_notifications' are disabled.
user_settings.enable_followed_topic_desktop_notifications = false; user_settings.enable_followed_topic_desktop_notifications = false;
user_settings.enable_followed_topic_audible_notifications = false; user_settings.enable_followed_topic_audible_notifications = false;
assert.equal(notifications.should_send_desktop_notification(message), false); assert.equal(message_notifications.should_send_desktop_notification(message), false);
assert.equal(notifications.should_send_audible_notification(message), false); assert.equal(message_notifications.should_send_audible_notification(message), false);
assert.equal(notifications.message_is_notifiable(message), true); assert.equal(message_notifications.message_is_notifiable(message), true);
// Reset state // Reset state
user_settings.enable_followed_topic_desktop_notifications = true; user_settings.enable_followed_topic_desktop_notifications = true;
@@ -168,9 +169,9 @@ test("message_is_notifiable", () => {
stream_id: general.stream_id, stream_id: general.stream_id,
topic: "vanilla", topic: "vanilla",
}; };
assert.equal(notifications.should_send_desktop_notification(message), true); assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true); assert.equal(message_notifications.should_send_audible_notification(message), true);
assert.equal(notifications.message_is_notifiable(message), true); assert.equal(message_notifications.message_is_notifiable(message), true);
// Case 6: // Case 6:
// Wildcard mention should trigger notification in unmuted topic // Wildcard mention should trigger notification in unmuted topic
@@ -186,21 +187,21 @@ test("message_is_notifiable", () => {
stream_id: general.stream_id, stream_id: general.stream_id,
topic: "vanilla", topic: "vanilla",
}; };
assert.equal(notifications.should_send_desktop_notification(message), true); assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true); assert.equal(message_notifications.should_send_audible_notification(message), true);
assert.equal(notifications.message_is_notifiable(message), true); assert.equal(message_notifications.message_is_notifiable(message), true);
// But not if it's disabled // But not if it's disabled
user_settings.wildcard_mentions_notify = false; user_settings.wildcard_mentions_notify = false;
assert.equal(notifications.should_send_desktop_notification(message), false); assert.equal(message_notifications.should_send_desktop_notification(message), false);
assert.equal(notifications.should_send_audible_notification(message), false); assert.equal(message_notifications.should_send_audible_notification(message), false);
assert.equal(notifications.message_is_notifiable(message), true); assert.equal(message_notifications.message_is_notifiable(message), true);
// And the stream-level setting overrides the global setting // And the stream-level setting overrides the global setting
general.wildcard_mentions_notify = true; general.wildcard_mentions_notify = true;
assert.equal(notifications.should_send_desktop_notification(message), true); assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true); assert.equal(message_notifications.should_send_audible_notification(message), true);
assert.equal(notifications.message_is_notifiable(message), true); assert.equal(message_notifications.message_is_notifiable(message), true);
// Reset state // Reset state
user_settings.wildcard_mentions_notify = true; user_settings.wildcard_mentions_notify = true;
@@ -220,9 +221,9 @@ test("message_is_notifiable", () => {
stream_id: muted.stream_id, stream_id: muted.stream_id,
topic: "whatever", topic: "whatever",
}; };
assert.equal(notifications.should_send_desktop_notification(message), true); assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true); assert.equal(message_notifications.should_send_audible_notification(message), true);
assert.equal(notifications.message_is_notifiable(message), false); assert.equal(message_notifications.message_is_notifiable(message), false);
// Case 8: If a message is in a muted stream // Case 8: If a message is in a muted stream
// and does mention the user DIRECTLY, // and does mention the user DIRECTLY,
@@ -238,9 +239,9 @@ test("message_is_notifiable", () => {
stream_id: muted.stream_id, stream_id: muted.stream_id,
topic: "whatever", topic: "whatever",
}; };
assert.equal(notifications.should_send_desktop_notification(message), true); assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true); assert.equal(message_notifications.should_send_audible_notification(message), true);
assert.equal(notifications.message_is_notifiable(message), true); assert.equal(message_notifications.message_is_notifiable(message), true);
// Case 9: If a message is in a muted topic // Case 9: If a message is in a muted topic
// and does not mention the user DIRECTLY (i.e. wildcard mention), // and does not mention the user DIRECTLY (i.e. wildcard mention),
@@ -256,9 +257,9 @@ test("message_is_notifiable", () => {
stream_id: general.stream_id, stream_id: general.stream_id,
topic: "muted topic", topic: "muted topic",
}; };
assert.equal(notifications.should_send_desktop_notification(message), true); assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true); assert.equal(message_notifications.should_send_audible_notification(message), true);
assert.equal(notifications.message_is_notifiable(message), false); assert.equal(message_notifications.message_is_notifiable(message), false);
// Case 10: // Case 10:
// Wildcard mentions in a followed topic with 'wildcard_mentions_notify', // Wildcard mentions in a followed topic with 'wildcard_mentions_notify',
@@ -280,15 +281,15 @@ test("message_is_notifiable", () => {
stream_id: general.stream_id, stream_id: general.stream_id,
topic: "followed topic", topic: "followed topic",
}; };
assert.equal(notifications.should_send_desktop_notification(message), true); assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), true); assert.equal(message_notifications.should_send_audible_notification(message), true);
assert.equal(notifications.message_is_notifiable(message), true); assert.equal(message_notifications.message_is_notifiable(message), true);
// But not if 'enable_followed_topic_wildcard_mentions_notify' is disabled // But not if 'enable_followed_topic_wildcard_mentions_notify' is disabled
user_settings.enable_followed_topic_wildcard_mentions_notify = false; user_settings.enable_followed_topic_wildcard_mentions_notify = false;
assert.equal(notifications.should_send_desktop_notification(message), false); assert.equal(message_notifications.should_send_desktop_notification(message), false);
assert.equal(notifications.should_send_audible_notification(message), false); assert.equal(message_notifications.should_send_audible_notification(message), false);
assert.equal(notifications.message_is_notifiable(message), true); assert.equal(message_notifications.message_is_notifiable(message), true);
// Reset state // Reset state
user_settings.wildcard_mentions_notify = true; user_settings.wildcard_mentions_notify = true;
@@ -310,9 +311,9 @@ test("message_is_notifiable", () => {
topic: "whatever", topic: "whatever",
}; };
user_settings.notification_sound = "none"; user_settings.notification_sound = "none";
assert.equal(notifications.should_send_desktop_notification(message), true); assert.equal(message_notifications.should_send_desktop_notification(message), true);
assert.equal(notifications.should_send_audible_notification(message), false); assert.equal(message_notifications.should_send_audible_notification(message), false);
assert.equal(notifications.message_is_notifiable(message), true); assert.equal(message_notifications.message_is_notifiable(message), true);
// Reset state // Reset state
user_settings.notification_sound = "ding"; user_settings.notification_sound = "ding";
@@ -332,9 +333,9 @@ test("message_is_notifiable", () => {
stream_id: general.stream_id, stream_id: general.stream_id,
topic: "whatever", topic: "whatever",
}; };
assert.equal(notifications.should_send_desktop_notification(message), false); assert.equal(message_notifications.should_send_desktop_notification(message), false);
assert.equal(notifications.should_send_audible_notification(message), false); assert.equal(message_notifications.should_send_audible_notification(message), false);
assert.equal(notifications.message_is_notifiable(message), true); assert.equal(message_notifications.message_is_notifiable(message), true);
}); });
test("basic_notifications", () => { test("basic_notifications", () => {
@@ -393,7 +394,7 @@ test("basic_notifications", () => {
}; };
// Send notification. // 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(); n = notifications.get_notifications();
assert.equal(n.has("Jesse Pinkman to general > whatever"), true); assert.equal(n.has("Jesse Pinkman to general > whatever"), true);
assert.equal(n.size, 1); assert.equal(n.size, 1);
@@ -408,7 +409,7 @@ test("basic_notifications", () => {
// Send notification. // Send notification.
message_1.id = 1001; 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(); n = notifications.get_notifications();
assert.equal(n.has("Jesse Pinkman to general > whatever"), true); assert.equal(n.has("Jesse Pinkman to general > whatever"), true);
assert.equal(n.size, 1); assert.equal(n.size, 1);
@@ -416,14 +417,14 @@ test("basic_notifications", () => {
// Process same message again. Notification count shouldn't increase. // Process same message again. Notification count shouldn't increase.
message_1.id = 1002; 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(); n = notifications.get_notifications();
assert.equal(n.has("Jesse Pinkman to general > whatever"), true); assert.equal(n.has("Jesse Pinkman to general > whatever"), true);
assert.equal(n.size, 1); assert.equal(n.size, 1);
assert.equal(last_shown_message_id, message_1.id); assert.equal(last_shown_message_id, message_1.id);
// Send another message. Notification count should increase. // 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(); n = notifications.get_notifications();
assert.equal(n.has("Gus Fring to general > lunch"), true); assert.equal(n.has("Gus Fring to general > lunch"), true);
assert.equal(n.has("Jesse Pinkman to general > whatever"), true); assert.equal(n.has("Jesse Pinkman to general > whatever"), true);