Push notifications: Fixes for not joined chats (#1099)

This commit is contained in:
Alexander Zinchuk 2021-05-14 01:35:28 +03:00
parent f964517a25
commit c94163803e
2 changed files with 57 additions and 17 deletions

View File

@ -74,11 +74,17 @@ function getNotificationData(data: PushData): NotificationData {
} }
function showNotification({ function showNotification({
chatId, messageId, body, title, chatId,
messageId,
body,
title,
}: NotificationData) { }: NotificationData) {
return self.registration.showNotification(title, { return self.registration.showNotification(title, {
body, body,
data: { chatId, messageId }, data: {
chatId,
messageId,
},
icon: 'icon-192x192.png', icon: 'icon-192x192.png',
badge: 'icon-192x192.png', badge: 'icon-192x192.png',
vibrate: [200, 100, 200], vibrate: [200, 100, 200],
@ -110,8 +116,11 @@ export function handlePush(e: PushEvent) {
e.waitUntil(showNotification(notification)); e.waitUntil(showNotification(notification));
} }
function focusChatMessage(client: WindowClient, data: { chatId?: number; messageId?: number }) { async function focusChatMessage(client: WindowClient, data: { chatId?: number; messageId?: number }) {
const { chatId, messageId } = data; const {
chatId,
messageId,
} = data;
if (!chatId) return; if (!chatId) return;
client.postMessage({ client.postMessage({
type: 'focusMessage', type: 'focusMessage',
@ -120,6 +129,15 @@ function focusChatMessage(client: WindowClient, data: { chatId?: number; message
messageId, messageId,
}, },
}); });
// Catch "focus not allowed" DOM Exceptions
try {
await client.focus();
} catch (error) {
if (DEBUG) {
// eslint-disable-next-line no-console
console.warn('[SW] ', error);
}
}
} }
export function handleNotificationClick(e: NotificationEvent) { export function handleNotificationClick(e: NotificationEvent) {
@ -129,10 +147,10 @@ export function handleNotificationClick(e: NotificationEvent) {
const notifyClients = async () => { const notifyClients = async () => {
const clients = await self.clients.matchAll({ type: 'window' }) as WindowClient[]; const clients = await self.clients.matchAll({ type: 'window' }) as WindowClient[];
const clientsInScope = clients.filter((client) => client.url === self.registration.scope); const clientsInScope = clients.filter((client) => client.url === self.registration.scope);
await Promise.all(clientsInScope.map(async (client) => { e.waitUntil(Promise.all(clientsInScope.map((client) => {
await client.focus();
clickBuffer[client.id] = data; clickBuffer[client.id] = data;
})); return focusChatMessage(client, data);
})));
if (!self.clients.openWindow || clientsInScope.length > 0) return undefined; if (!self.clients.openWindow || clientsInScope.length > 0) return undefined;
// If there is no opened client we need to open one and wait until it is fully loaded // If there is no opened client we need to open one and wait until it is fully loaded
@ -147,12 +165,16 @@ export function handleNotificationClick(e: NotificationEvent) {
} }
export function handleClientMessage(e: ExtendableMessageEvent) { export function handleClientMessage(e: ExtendableMessageEvent) {
if (DEBUG) {
// eslint-disable-next-line no-console
console.log('[SW] New message from client', e);
}
if (!e.data) return; if (!e.data) return;
const source = e.source as WindowClient; const source = e.source as WindowClient;
if (e.data.type === 'clientReady') { if (e.data.type === 'clientReady') {
// focus on chat message when client is fully ready // focus on chat message when client is fully ready
if (clickBuffer[source.id]) { if (clickBuffer[source.id]) {
focusChatMessage(source, clickBuffer[source.id]); e.waitUntil(focusChatMessage(source, clickBuffer[source.id]));
delete clickBuffer[source.id]; delete clickBuffer[source.id];
} }
} }

View File

@ -2,7 +2,7 @@ import { callApi } from '../api/gramjs';
import { ApiChat, ApiMessage } from '../api/types'; import { ApiChat, ApiMessage } from '../api/types';
import { renderActionMessageText } from '../components/common/helpers/renderActionMessageText'; import { renderActionMessageText } from '../components/common/helpers/renderActionMessageText';
import { DEBUG } from '../config'; import { DEBUG } from '../config';
import { getDispatch, getGlobal } from '../lib/teact/teactn'; import { getDispatch, getGlobal, setGlobal } from '../lib/teact/teactn';
import { import {
getChatTitle, getChatTitle,
getMessageAction, getMessageAction,
@ -12,6 +12,7 @@ import {
isActionMessage, isActionMessage,
isChatChannel, isChatChannel,
} from '../modules/helpers'; } from '../modules/helpers';
import { replaceSettings } from '../modules/reducers';
import { selectChatMessage, selectUser } from '../modules/selectors'; import { selectChatMessage, selectUser } from '../modules/selectors';
import { IS_SERVICE_WORKER_SUPPORTED } from './environment'; import { IS_SERVICE_WORKER_SUPPORTED } from './environment';
@ -122,9 +123,22 @@ export async function unsubscribe() {
await unsubscribeFromPush(subscription); await unsubscribeFromPush(subscription);
} }
// Load notification settings from the api
async function loadNotificationsSettings() {
const result = await callApi('loadNotificationsSettings');
if (!result) return;
setGlobal(replaceSettings(getGlobal(), result));
}
export async function subscribe() { export async function subscribe() {
await requestPermission(); loadNotificationsSettings();
if (!checkIfPushSupported()) return;
if (!checkIfPushSupported()) {
// Ask for notification permissions only if service worker notifications are not supported
// As pushManager.subscribe automatically triggers permission popup
await requestPermission();
return;
}
const serviceWorkerRegistration = await navigator.serviceWorker.ready; const serviceWorkerRegistration = await navigator.serviceWorker.ready;
let subscription = await serviceWorkerRegistration.pushManager.getSubscription(); let subscription = await serviceWorkerRegistration.pushManager.getSubscription();
if (!checkIfShouldResubscribe(subscription)) return; if (!checkIfShouldResubscribe(subscription)) return;
@ -157,17 +171,22 @@ export async function subscribe() {
// and / or gcm_user_visible_only // and / or gcm_user_visible_only
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('[PUSH] Unable to subscribe to push.', error); console.log('[PUSH] Unable to subscribe to push.', error);
// Request permissions and fall back to local notifications
// if pushManager.subscribe was aborted due to invalid VAPID key.
if (error.code === DOMException.ABORT_ERR) {
await requestPermission();
}
} }
} }
} }
async function checkIfShouldNotify(chat: ApiChat, isActive: boolean) { function checkIfShouldNotify(chat: ApiChat, isActive: boolean) {
if (chat.isMuted) return false; if (chat.isMuted || chat.isNotJoined) return false;
// Dont show notification for active chat if client has focus // Dont show notification for active chat if client has focus
if (isActive && document.hasFocus()) return false; if (isActive && document.hasFocus()) return false;
await getDispatch().loadNotificationsSettings();
const global = getGlobal(); const global = getGlobal();
switch (chat.type) { switch (chat.type) {
case 'chatTypePrivate': case 'chatTypePrivate':
@ -226,7 +245,7 @@ function getNotificationContent(chat: ApiChat, message: ApiMessage) {
}; };
} }
export async function showNewMessageNotification({ export function showNewMessageNotification({
chat, chat,
message, message,
isActiveChat, isActiveChat,
@ -234,8 +253,7 @@ export async function showNewMessageNotification({
if (!checkIfNotificationsSupported()) return; if (!checkIfNotificationsSupported()) return;
if (!message.id) return; if (!message.id) return;
const shouldNotify = await checkIfShouldNotify(chat, isActiveChat); if (!checkIfShouldNotify(chat, isActiveChat)) return;
if (!shouldNotify) return;
const { const {
title, title,