Push Notifications: Initial support (#1046)
This commit is contained in:
parent
e15ca141ae
commit
dd3158001e
@ -2,6 +2,7 @@
|
|||||||
"name": "Telegram WebZ",
|
"name": "Telegram WebZ",
|
||||||
"short_name": "Telegram WebZ",
|
"short_name": "Telegram WebZ",
|
||||||
"start_url": "/",
|
"start_url": "/",
|
||||||
|
"gcm_sender_id": "648693842861",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "android-chrome-192x192.png",
|
"src": "android-chrome-192x192.png",
|
||||||
@ -21,5 +22,8 @@
|
|||||||
],
|
],
|
||||||
"theme_color": "#ffffff",
|
"theme_color": "#ffffff",
|
||||||
"background_color": "#ffffff",
|
"background_color": "#ffffff",
|
||||||
"display": "standalone"
|
"display": "standalone",
|
||||||
|
"permissions": [
|
||||||
|
"notifications"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -94,6 +94,10 @@ export async function destroy() {
|
|||||||
await client.destroy();
|
await client.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
function handleGramJsUpdate(update: any) {
|
function handleGramJsUpdate(update: any) {
|
||||||
if (update instanceof connection.UpdateConnectionState) {
|
if (update instanceof connection.UpdateConnectionState) {
|
||||||
isConnected = update.state === connection.UpdateConnectionState.connected;
|
isConnected = update.state === connection.UpdateConnectionState.connected;
|
||||||
|
|||||||
@ -44,7 +44,7 @@ export {
|
|||||||
updateProfilePhoto, uploadProfilePhoto, fetchWallpapers, uploadWallpaper,
|
updateProfilePhoto, uploadProfilePhoto, fetchWallpapers, uploadWallpaper,
|
||||||
fetchAuthorizations, terminateAuthorization, terminateAllAuthorizations,
|
fetchAuthorizations, terminateAuthorization, terminateAllAuthorizations,
|
||||||
loadNotificationsSettings, updateContactSignUpNotification, updateNotificationSettings,
|
loadNotificationsSettings, updateContactSignUpNotification, updateNotificationSettings,
|
||||||
fetchLanguages, fetchLangPack, fetchPrivacySettings, setPrivacySettings,
|
fetchLanguages, fetchLangPack, fetchPrivacySettings, setPrivacySettings, registerDevice, unregisterDevice,
|
||||||
} from './settings';
|
} from './settings';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { buildApiWallpaper, buildApiSession, buildPrivacyRules } from '../apiBui
|
|||||||
import { buildApiUser } from '../apiBuilders/users';
|
import { buildApiUser } from '../apiBuilders/users';
|
||||||
import { buildApiChatFromPreview, getApiChatIdFromMtpPeer } from '../apiBuilders/chats';
|
import { buildApiChatFromPreview, getApiChatIdFromMtpPeer } from '../apiBuilders/chats';
|
||||||
import { buildInputPrivacyKey, buildInputPeer, buildPeer } from '../gramjsBuilders';
|
import { buildInputPrivacyKey, buildInputPeer, buildPeer } from '../gramjsBuilders';
|
||||||
import { invokeRequest, uploadFile } from './client';
|
import { invokeRequest, uploadFile, getClient } from './client';
|
||||||
import { omitVirtualClassFields } from '../apiBuilders/helpers';
|
import { omitVirtualClassFields } from '../apiBuilders/helpers';
|
||||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||||
import localDb from '../localDb';
|
import localDb from '../localDb';
|
||||||
@ -279,6 +279,26 @@ export async function fetchPrivacySettings(privacyKey: ApiPrivacyKey) {
|
|||||||
return buildPrivacyRules(result.rules);
|
return buildPrivacyRules(result.rules);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function registerDevice(token: string) {
|
||||||
|
const client = getClient();
|
||||||
|
const secret = client.session.getAuthKey().getKey();
|
||||||
|
return invokeRequest(new GramJs.account.RegisterDevice({
|
||||||
|
tokenType: 10,
|
||||||
|
secret,
|
||||||
|
appSandbox: false,
|
||||||
|
otherUids: [],
|
||||||
|
token,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unregisterDevice(token: string) {
|
||||||
|
return invokeRequest(new GramJs.account.UnregisterDevice({
|
||||||
|
tokenType: 10,
|
||||||
|
otherUids: [],
|
||||||
|
token,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
export async function setPrivacySettings(
|
export async function setPrivacySettings(
|
||||||
privacyKey: ApiPrivacyKey, rules: IInputPrivacyRules,
|
privacyKey: ApiPrivacyKey, rules: IInputPrivacyRules,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@ -867,6 +867,8 @@ account.sendVerifyPhoneCode#a5a356f9 phone_number:string settings:CodeSettings =
|
|||||||
account.confirmPasswordEmail#8fdf1920 code:string = Bool;
|
account.confirmPasswordEmail#8fdf1920 code:string = Bool;
|
||||||
account.getContactSignUpNotification#9f07c728 = Bool;
|
account.getContactSignUpNotification#9f07c728 = Bool;
|
||||||
account.setContactSignUpNotification#cff43f61 silent:Bool = Bool;
|
account.setContactSignUpNotification#cff43f61 silent:Bool = Bool;
|
||||||
|
account.registerDevice#68976c6f flags:# no_muted:flags.0?true token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector<int> = Bool;
|
||||||
|
account.unregisterDevice#3076c4bf token_type:int token:string other_uids:Vector<int> = Bool;
|
||||||
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
|
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
|
||||||
users.getFullUser#ca30a5b1 id:InputUser = UserFull;
|
users.getFullUser#ca30a5b1 id:InputUser = UserFull;
|
||||||
contacts.getContacts#c023849f hash:int = contacts.Contacts;
|
contacts.getContacts#c023849f hash:int = contacts.Contacts;
|
||||||
|
|||||||
@ -867,6 +867,8 @@ account.sendVerifyPhoneCode#a5a356f9 phone_number:string settings:CodeSettings =
|
|||||||
account.confirmPasswordEmail#8fdf1920 code:string = Bool;
|
account.confirmPasswordEmail#8fdf1920 code:string = Bool;
|
||||||
account.getContactSignUpNotification#9f07c728 = Bool;
|
account.getContactSignUpNotification#9f07c728 = Bool;
|
||||||
account.setContactSignUpNotification#cff43f61 silent:Bool = Bool;
|
account.setContactSignUpNotification#cff43f61 silent:Bool = Bool;
|
||||||
|
account.registerDevice#68976c6f flags:# no_muted:flags.0?true token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector<int> = Bool;
|
||||||
|
account.unregisterDevice#3076c4bf token_type:int token:string other_uids:Vector<int> = Bool;
|
||||||
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
|
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
|
||||||
users.getFullUser#ca30a5b1 id:InputUser = UserFull;
|
users.getFullUser#ca30a5b1 id:InputUser = UserFull;
|
||||||
contacts.getContacts#c023849f hash:int = contacts.Contacts;
|
contacts.getContacts#c023849f hash:int = contacts.Contacts;
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import {
|
|||||||
ApiUpdateCurrentUser,
|
ApiUpdateCurrentUser,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import { DEBUG } from '../../../config';
|
import { DEBUG } from '../../../config';
|
||||||
|
import { setupPushNotifications } from '../../../util/setupPushNotifications';
|
||||||
import { updateUser } from '../../reducers';
|
import { updateUser } from '../../reducers';
|
||||||
import { setLanguage } from '../../../util/langProvider';
|
import { setLanguage } from '../../../util/langProvider';
|
||||||
|
|
||||||
@ -138,6 +139,7 @@ function onUpdateConnectionState(update: ApiUpdateConnectionState) {
|
|||||||
|
|
||||||
if (connectionState === 'connectionStateReady' && global.authState === 'authorizationStateReady') {
|
if (connectionState === 'connectionStateReady' && global.authState === 'authorizationStateReady') {
|
||||||
getDispatch().sync();
|
getDispatch().sync();
|
||||||
|
setupPushNotifications();
|
||||||
} else if (connectionState === 'connectionStateBroken') {
|
} else if (connectionState === 'connectionStateBroken') {
|
||||||
getDispatch().signOut();
|
getDispatch().signOut();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,3 +43,56 @@ self.addEventListener('fetch', (e: FetchEvent) => {
|
|||||||
return fetch(e.request);
|
return fetch(e.request);
|
||||||
})());
|
})());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
85
src/util/setupPushNotifications.ts
Normal file
85
src/util/setupPushNotifications.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { callApi } from '../api/gramjs';
|
||||||
|
import { DEBUG } from '../config';
|
||||||
|
|
||||||
|
function getDeviceToken(subscription: PushSubscription) {
|
||||||
|
const data = subscription.toJSON();
|
||||||
|
return JSON.stringify({ endpoint: data.endpoint, keys: data.keys });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setupPushNotifications() {
|
||||||
|
if (!('showNotification' in ServiceWorkerRegistration.prototype)) {
|
||||||
|
if (DEBUG) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('[PUSH] Push notifications aren\'t supported.');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the current Notification permission.
|
||||||
|
// If its denied, it's a permanent block until the
|
||||||
|
// user changes the permission
|
||||||
|
if (Notification.permission === 'denied') {
|
||||||
|
if (DEBUG) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('[PUSH] The user has blocked push notifications.');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if push messaging is supported
|
||||||
|
if (!('PushManager' in window)) {
|
||||||
|
if (DEBUG) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('[PUSH] Push messaging isn\'t supported.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceWorkerRegistration = await navigator.serviceWorker.ready;
|
||||||
|
let subscription = await serviceWorkerRegistration.pushManager.getSubscription();
|
||||||
|
if (subscription) {
|
||||||
|
try {
|
||||||
|
const deviceToken = getDeviceToken(subscription);
|
||||||
|
await callApi('unregisterDevice', deviceToken);
|
||||||
|
await subscription.unsubscribe();
|
||||||
|
} catch (error) {
|
||||||
|
if (DEBUG) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('[PUSH] Unable to unsubscribe from push.', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (Notification.permission === 'denied' as NotificationPermission) {
|
||||||
|
// The user denied the notification permission which
|
||||||
|
// means we failed to subscribe and the user will need
|
||||||
|
// to manually change the notification permission to
|
||||||
|
// subscribe to push messages
|
||||||
|
if (DEBUG) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('[PUSH] Permission for Notifications was denied');
|
||||||
|
}
|
||||||
|
} else if (DEBUG) {
|
||||||
|
// A problem occurred with the subscription, this can
|
||||||
|
// often be down to an issue or lack of the gcm_sender_id
|
||||||
|
// and / or gcm_user_visible_only
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('[PUSH] Unable to subscribe to push.', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import { scriptUrl } from 'service-worker-loader!../serviceWorker';
|
import { scriptUrl } from 'service-worker-loader!../serviceWorker';
|
||||||
|
|
||||||
import { DEBUG } from '../config';
|
import { DEBUG } from '../config';
|
||||||
import { IS_SERVICE_WORKER_SUPPORTED } from './environment';
|
|
||||||
import { getDispatch } from '../lib/teact/teactn';
|
import { getDispatch } from '../lib/teact/teactn';
|
||||||
|
import { IS_SERVICE_WORKER_SUPPORTED } from './environment';
|
||||||
|
|
||||||
if (IS_SERVICE_WORKER_SUPPORTED) {
|
if (IS_SERVICE_WORKER_SUPPORTED) {
|
||||||
window.addEventListener('load', async () => {
|
window.addEventListener('load', async () => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user