mirror of
https://github.com/danog/telegram-tt.git
synced 2024-11-30 04:39:00 +01:00
Push notifications: Use existing browser tab, unsubscribe on sign out (#1053)
This commit is contained in:
parent
b952967bdd
commit
9c4cb209c0
@ -1,34 +1,34 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
import React, {
|
||||
FC, useState, useEffect, useCallback, useLayoutEffect, useRef, memo,
|
||||
} from '../../lib/teact/teact';
|
||||
import { withGlobal } from '../../lib/teact/teactn';
|
||||
|
||||
import { GlobalState, GlobalActions } from '../../global/types';
|
||||
|
||||
import {
|
||||
MEDIA_CACHE_NAME,
|
||||
MEDIA_CACHE_NAME_AVATARS,
|
||||
MEDIA_PROGRESSIVE_CACHE_NAME,
|
||||
CUSTOM_BG_CACHE_NAME,
|
||||
LANG_CACHE_NAME,
|
||||
} from '../../config';
|
||||
import { IS_TOUCH_ENV } from '../../util/environment';
|
||||
import * as cacheApi from '../../util/cacheApi';
|
||||
import { formatPhoneNumber, getCountryFromPhoneNumber, getCountryById } from '../../util/phoneNumber';
|
||||
import preloadFonts from '../../util/fonts';
|
||||
import { preloadImage } from '../../util/files';
|
||||
import { pick } from '../../util/iteratees';
|
||||
|
||||
import Button from '../ui/Button';
|
||||
import InputText from '../ui/InputText';
|
||||
import CountryCodeInput from './CountryCodeInput';
|
||||
import Checkbox from '../ui/Checkbox';
|
||||
import Loading from '../ui/Loading';
|
||||
|
||||
// @ts-ignore
|
||||
import monkeyPath from '../../assets/monkey.svg';
|
||||
|
||||
import {
|
||||
CUSTOM_BG_CACHE_NAME,
|
||||
LANG_CACHE_NAME,
|
||||
MEDIA_CACHE_NAME,
|
||||
MEDIA_CACHE_NAME_AVATARS,
|
||||
MEDIA_PROGRESSIVE_CACHE_NAME,
|
||||
} from '../../config';
|
||||
|
||||
import { GlobalActions, GlobalState } from '../../global/types';
|
||||
import React, {
|
||||
FC, memo, useCallback, useEffect, useLayoutEffect, useRef, useState,
|
||||
} from '../../lib/teact/teact';
|
||||
import { withGlobal } from '../../lib/teact/teactn';
|
||||
import * as cacheApi from '../../util/cacheApi';
|
||||
import { IS_TOUCH_ENV } from '../../util/environment';
|
||||
import { preloadImage } from '../../util/files';
|
||||
import preloadFonts from '../../util/fonts';
|
||||
import { pick } from '../../util/iteratees';
|
||||
import { formatPhoneNumber, getCountryById, getCountryFromPhoneNumber } from '../../util/phoneNumber';
|
||||
|
||||
import Button from '../ui/Button';
|
||||
import Checkbox from '../ui/Checkbox';
|
||||
import InputText from '../ui/InputText';
|
||||
import Loading from '../ui/Loading';
|
||||
import CountryCodeInput from './CountryCodeInput';
|
||||
|
||||
type StateProps = Pick<GlobalState, (
|
||||
'connectionState' | 'authState' |
|
||||
'authPhoneNumber' | 'authIsLoading' | 'authIsLoadingQrCode' | 'authError' | 'authRememberMe' | 'authNearestCountry'
|
||||
|
@ -6,6 +6,7 @@ import { GlobalState } from '../../../global/types';
|
||||
|
||||
import { GRAMJS_SESSION_ID_KEY } from '../../../config';
|
||||
import { initApi, callApi } from '../../../api/gramjs';
|
||||
import { unsubscribeFromPush } from '../../../util/pushNotifications';
|
||||
|
||||
addReducer('initApi', (global: GlobalState, actions) => {
|
||||
const sessionId = localStorage.getItem(GRAMJS_SESSION_ID_KEY) || undefined;
|
||||
@ -101,6 +102,7 @@ addReducer('signOut', () => {
|
||||
});
|
||||
|
||||
async function signOut() {
|
||||
await unsubscribeFromPush();
|
||||
await callApi('destroy');
|
||||
localStorage.removeItem(GRAMJS_SESSION_ID_KEY);
|
||||
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
ApiUpdateCurrentUser,
|
||||
} from '../../../api/types';
|
||||
import { DEBUG } from '../../../config';
|
||||
import { setupPushNotifications } from '../../../util/setupPushNotifications';
|
||||
import { subscribeToPush } from '../../../util/pushNotifications';
|
||||
import { updateUser } from '../../reducers';
|
||||
import { setLanguage } from '../../../util/langProvider';
|
||||
|
||||
@ -57,6 +57,7 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
||||
});
|
||||
|
||||
function onUpdateApiReady(global: GlobalState) {
|
||||
subscribeToPush();
|
||||
setLanguage(global.settings.byKey.language);
|
||||
}
|
||||
|
||||
@ -139,7 +140,6 @@ function onUpdateConnectionState(update: ApiUpdateConnectionState) {
|
||||
|
||||
if (connectionState === 'connectionStateReady' && global.authState === 'authorizationStateReady') {
|
||||
getDispatch().sync();
|
||||
setupPushNotifications();
|
||||
} else if (connectionState === 'connectionStateBroken') {
|
||||
getDispatch().signOut();
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { DEBUG } from './config';
|
||||
import { respondForProgressive } from './serviceWorker/progressive';
|
||||
import { respondWithCache, clearAssetCache } from './serviceWorker/assetCache';
|
||||
import { handlePush, handleNotificationClick } from './serviceWorker/pushNotification';
|
||||
|
||||
declare const self: ServiceWorkerGlobalScope;
|
||||
|
||||
@ -45,54 +46,5 @@ self.addEventListener('fetch', (e: FetchEvent) => {
|
||||
});
|
||||
|
||||
|
||||
self.addEventListener('push', (e: PushEvent) => {
|
||||
if (DEBUG) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[SW] Push received event', e);
|
||||
if (e.data) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[SW] Push received with data "${e.data.text()}"`);
|
||||
}
|
||||
}
|
||||
if (!e.data) return;
|
||||
let obj;
|
||||
try {
|
||||
obj = e.data.json();
|
||||
} catch (error) {
|
||||
obj = e.data.text();
|
||||
}
|
||||
|
||||
const title = obj.title || 'Telegram';
|
||||
const body = obj.description || obj;
|
||||
const options = {
|
||||
body,
|
||||
icon: 'android-chrome-192x192.png',
|
||||
};
|
||||
|
||||
e.waitUntil(
|
||||
self.registration.showNotification(title, options),
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('notificationclick', (event) => {
|
||||
const url = '/';
|
||||
event.notification.close(); // Android needs explicit close.
|
||||
event.waitUntil(
|
||||
self.clients.matchAll({ type: 'window' })
|
||||
.then((windowClients) => {
|
||||
// Check if there is already a window/tab open with the target URL
|
||||
for (let i = 0; i < windowClients.length; i++) {
|
||||
const client = windowClients[i] as WindowClient;
|
||||
// If so, just focus it.
|
||||
if (client.url === url && client.focus) {
|
||||
client.focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If not, then open the target URL in a new window/tab.
|
||||
if (self.clients.openWindow) {
|
||||
self.clients.openWindow(url);
|
||||
}
|
||||
}),
|
||||
);
|
||||
});
|
||||
self.addEventListener('push', handlePush);
|
||||
self.addEventListener('notificationclick', handleNotificationClick);
|
||||
|
80
src/serviceWorker/pushNotification.ts
Normal file
80
src/serviceWorker/pushNotification.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import { DEBUG } from '../config';
|
||||
|
||||
declare const self: ServiceWorkerGlobalScope;
|
||||
|
||||
export enum NotificationType {
|
||||
MESSAGE_TEXT = 'MESSAGE_TEXT',
|
||||
MESSAGE_NOTEXT = 'MESSAGE_NOTEXT',
|
||||
MESSAGE_STICKER = 'MESSAGE_STICKER'
|
||||
}
|
||||
|
||||
export type NotificationData = {
|
||||
custom: {
|
||||
msg_id: string;
|
||||
from_id: string;
|
||||
};
|
||||
mute: '0' | '1';
|
||||
badge: '0' | '1';
|
||||
loc_key: NotificationType;
|
||||
loc_args: string[];
|
||||
random_id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
|
||||
export function handlePush(e: PushEvent) {
|
||||
if (DEBUG) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[SW] Push received event', e);
|
||||
if (e.data) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[SW] Push received with data', e.data.json());
|
||||
}
|
||||
}
|
||||
if (!e.data) return;
|
||||
let data: NotificationData;
|
||||
try {
|
||||
data = e.data.json();
|
||||
} catch (error) {
|
||||
if (DEBUG) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[SW] Unable to parse push notification data', e.data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const title = data.title || process.env.APP_INFO!;
|
||||
const body = data.description;
|
||||
const options = {
|
||||
body,
|
||||
icon: 'android-chrome-192x192.png',
|
||||
};
|
||||
|
||||
e.waitUntil(
|
||||
self.registration.showNotification(title, options),
|
||||
);
|
||||
}
|
||||
|
||||
export function handleNotificationClick(e: NotificationEvent) {
|
||||
const appUrl = process.env.APP_URL!;
|
||||
e.notification.close(); // Android needs explicit close.
|
||||
e.waitUntil(
|
||||
self.clients.matchAll({ type: 'window' })
|
||||
.then((windowClients) => {
|
||||
// Check if there is already a window/tab open with the target URL
|
||||
for (let i = 0; i < windowClients.length; i++) {
|
||||
const client = windowClients[i] as WindowClient;
|
||||
// If so, just focus it.
|
||||
if (client.url === self.registration.scope && client.focus) {
|
||||
client.focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If not, then open the target URL in a new window/tab.
|
||||
if (self.clients.openWindow) {
|
||||
self.clients.openWindow(appUrl);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
@ -1,18 +1,20 @@
|
||||
import { callApi } from '../api/gramjs';
|
||||
import { DEBUG } from '../config';
|
||||
import { IS_SERVICE_WORKER_SUPPORTED } from './environment';
|
||||
|
||||
function getDeviceToken(subscription: PushSubscription) {
|
||||
const data = subscription.toJSON();
|
||||
return JSON.stringify({ endpoint: data.endpoint, keys: data.keys });
|
||||
}
|
||||
|
||||
export async function setupPushNotifications() {
|
||||
export function isPushSupported() {
|
||||
if (!IS_SERVICE_WORKER_SUPPORTED) return false;
|
||||
if (!('showNotification' in ServiceWorkerRegistration.prototype)) {
|
||||
if (DEBUG) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[PUSH] Push notifications aren\'t supported.');
|
||||
}
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the current Notification permission.
|
||||
@ -23,7 +25,7 @@ export async function setupPushNotifications() {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[PUSH] The user has blocked push notifications.');
|
||||
}
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if push messaging is supported
|
||||
@ -32,11 +34,20 @@ export async function setupPushNotifications() {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[PUSH] Push messaging isn\'t supported.');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function unsubscribeFromPush() {
|
||||
if (!isPushSupported) return;
|
||||
const serviceWorkerRegistration = await navigator.serviceWorker.ready;
|
||||
let subscription = await serviceWorkerRegistration.pushManager.getSubscription();
|
||||
const subscription = await serviceWorkerRegistration.pushManager.getSubscription();
|
||||
if (subscription) {
|
||||
if (DEBUG) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[PUSH] Unsubscribing', subscription);
|
||||
}
|
||||
try {
|
||||
const deviceToken = getDeviceToken(subscription);
|
||||
await callApi('unregisterDevice', deviceToken);
|
||||
@ -48,22 +59,22 @@ export async function setupPushNotifications() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function subscribeToPush() {
|
||||
if (!isPushSupported()) return;
|
||||
await unsubscribeFromPush();
|
||||
const serviceWorkerRegistration = await navigator.serviceWorker.ready;
|
||||
try {
|
||||
subscription = await serviceWorkerRegistration.pushManager.subscribe({
|
||||
const subscription = await serviceWorkerRegistration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
});
|
||||
|
||||
const deviceToken = getDeviceToken(subscription);
|
||||
if (DEBUG) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[PUSH] Received push subscription: ', deviceToken);
|
||||
}
|
||||
const result = await callApi('registerDevice', deviceToken);
|
||||
if (DEBUG) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[PUSH] registerDevice result', result);
|
||||
}
|
||||
await callApi('registerDevice', deviceToken);
|
||||
} catch (error) {
|
||||
if (Notification.permission === 'denied' as NotificationPermission) {
|
||||
// The user denied the notification permission which
|
@ -105,6 +105,7 @@ module.exports = (env = {}, argv = {}) => {
|
||||
new EnvironmentPlugin({
|
||||
APP_INFO: 'Telegram T',
|
||||
APP_ENV: 'production',
|
||||
APP_URL: 'https://webz.telegram.org/',
|
||||
TELEGRAM_T_API_ID: '',
|
||||
TELEGRAM_T_API_HASH: '',
|
||||
}),
|
||||
|
Loading…
Reference in New Issue
Block a user