Push Notifications: Initial support (#1046)
This commit is contained in:
parent
e15ca141ae
commit
dd3158001e
@ -2,6 +2,7 @@
|
||||
"name": "Telegram WebZ",
|
||||
"short_name": "Telegram WebZ",
|
||||
"start_url": "/",
|
||||
"gcm_sender_id": "648693842861",
|
||||
"icons": [
|
||||
{
|
||||
"src": "android-chrome-192x192.png",
|
||||
@ -21,5 +22,8 @@
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
"display": "standalone",
|
||||
"permissions": [
|
||||
"notifications"
|
||||
]
|
||||
}
|
||||
|
||||
@ -94,6 +94,10 @@ export async function destroy() {
|
||||
await client.destroy();
|
||||
}
|
||||
|
||||
export function getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
function handleGramJsUpdate(update: any) {
|
||||
if (update instanceof connection.UpdateConnectionState) {
|
||||
isConnected = update.state === connection.UpdateConnectionState.connected;
|
||||
|
||||
@ -44,7 +44,7 @@ export {
|
||||
updateProfilePhoto, uploadProfilePhoto, fetchWallpapers, uploadWallpaper,
|
||||
fetchAuthorizations, terminateAuthorization, terminateAllAuthorizations,
|
||||
loadNotificationsSettings, updateContactSignUpNotification, updateNotificationSettings,
|
||||
fetchLanguages, fetchLangPack, fetchPrivacySettings, setPrivacySettings,
|
||||
fetchLanguages, fetchLangPack, fetchPrivacySettings, setPrivacySettings, registerDevice, unregisterDevice,
|
||||
} from './settings';
|
||||
|
||||
export {
|
||||
|
||||
@ -12,7 +12,7 @@ import { buildApiWallpaper, buildApiSession, buildPrivacyRules } from '../apiBui
|
||||
import { buildApiUser } from '../apiBuilders/users';
|
||||
import { buildApiChatFromPreview, getApiChatIdFromMtpPeer } from '../apiBuilders/chats';
|
||||
import { buildInputPrivacyKey, buildInputPeer, buildPeer } from '../gramjsBuilders';
|
||||
import { invokeRequest, uploadFile } from './client';
|
||||
import { invokeRequest, uploadFile, getClient } from './client';
|
||||
import { omitVirtualClassFields } from '../apiBuilders/helpers';
|
||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||
import localDb from '../localDb';
|
||||
@ -279,6 +279,26 @@ export async function fetchPrivacySettings(privacyKey: ApiPrivacyKey) {
|
||||
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(
|
||||
privacyKey: ApiPrivacyKey, rules: IInputPrivacyRules,
|
||||
) {
|
||||
|
||||
@ -867,6 +867,8 @@ account.sendVerifyPhoneCode#a5a356f9 phone_number:string settings:CodeSettings =
|
||||
account.confirmPasswordEmail#8fdf1920 code:string = Bool;
|
||||
account.getContactSignUpNotification#9f07c728 = 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.getFullUser#ca30a5b1 id:InputUser = UserFull;
|
||||
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.getContactSignUpNotification#9f07c728 = 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.getFullUser#ca30a5b1 id:InputUser = UserFull;
|
||||
contacts.getContacts#c023849f hash:int = contacts.Contacts;
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
ApiUpdateCurrentUser,
|
||||
} from '../../../api/types';
|
||||
import { DEBUG } from '../../../config';
|
||||
import { setupPushNotifications } from '../../../util/setupPushNotifications';
|
||||
import { updateUser } from '../../reducers';
|
||||
import { setLanguage } from '../../../util/langProvider';
|
||||
|
||||
@ -138,6 +139,7 @@ function onUpdateConnectionState(update: ApiUpdateConnectionState) {
|
||||
|
||||
if (connectionState === 'connectionStateReady' && global.authState === 'authorizationStateReady') {
|
||||
getDispatch().sync();
|
||||
setupPushNotifications();
|
||||
} else if (connectionState === 'connectionStateBroken') {
|
||||
getDispatch().signOut();
|
||||
}
|
||||
|
||||
@ -43,3 +43,56 @@ self.addEventListener('fetch', (e: FetchEvent) => {
|
||||
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 { DEBUG } from '../config';
|
||||
import { IS_SERVICE_WORKER_SUPPORTED } from './environment';
|
||||
import { getDispatch } from '../lib/teact/teactn';
|
||||
import { IS_SERVICE_WORKER_SUPPORTED } from './environment';
|
||||
|
||||
if (IS_SERVICE_WORKER_SUPPORTED) {
|
||||
window.addEventListener('load', async () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user