tweb/src/lib/mtproto/webPushApiManager.ts
Eduard Kuzmenko e4ac7cdf88 [Streaming] Fix hanged chunk load promise
Hide 'Sensitive content' section if it's unavailable
2021-07-06 19:11:00 +03:00

251 lines
7.8 KiB
TypeScript

/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*
* Originally from:
* https://github.com/zhukov/webogram
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
import type { NotificationSettings } from "../appManagers/appNotificationsManager";
import { MOUNT_CLASS_TO } from "../../config/debug";
import { copy } from "../../helpers/object";
import { logger } from "../logger";
import rootScope from "../rootScope";
import { ServiceWorkerNotificationsClearTask, ServiceWorkerPingTask, ServiceWorkerPushClickTask } from "../serviceWorker/index.service";
import apiManager from "./mtprotoworker";
import I18n, { LangPackKey } from "../langPack";
import { isMobile } from "../../helpers/userAgent";
import appRuntimeManager from "../appManagers/appRuntimeManager";
export type PushSubscriptionNotifyType = 'init' | 'subscribe' | 'unsubscribe';
export type PushSubscriptionNotifyEvent = `push_${PushSubscriptionNotifyType}`;
export type PushSubscriptionNotify = {
tokenType: number,
tokenValue: string
};
export class WebPushApiManager {
public isAvailable = true;
private isPushEnabled = false;
private localNotificationsAvailable = true;
private started = false;
private settings: NotificationSettings & {baseUrl?: string} = {} as any;
private isAliveTO: any;
private isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
private userVisibleOnly = this.isFirefox ? false : true;
private log = logger('PM');
constructor() {
if(!('PushManager' in window) ||
!('Notification' in window) ||
!('serviceWorker' in navigator)) {
this.log.warn('Push messaging is not supported.');
this.isAvailable = false;
this.localNotificationsAvailable = false;
}
if(this.isAvailable && Notification.permission === 'denied') {
this.log.warn('The user has blocked notifications.');
}
}
public start() {
if(!this.started) {
this.started = true;
this.getSubscription();
this.setUpServiceWorkerChannel();
}
}
public setLocalNotificationsDisabled() {
this.localNotificationsAvailable = false;
}
public getSubscription() {
if(!this.isAvailable) {
return;
}
navigator.serviceWorker.ready.then((reg) => {
reg.pushManager.getSubscription().then((subscription) => {
this.isPushEnabled = !!subscription;
this.pushSubscriptionNotify('init', subscription);
}).catch((err) => {
this.log.error('Error during getSubscription()', err);
});
});
}
public subscribe = () => {
if(!this.isAvailable) {
return;
}
navigator.serviceWorker.ready.then((reg) => {
reg.pushManager.subscribe({userVisibleOnly: this.userVisibleOnly}).then((subscription) => {
// The subscription was successful
this.isPushEnabled = true;
this.pushSubscriptionNotify('subscribe', subscription);
}).catch((e) => {
if(Notification.permission === 'denied') {
this.log('Permission for Notifications was denied');
} else {
this.log('Unable to subscribe to push.', e);
if(!this.userVisibleOnly) {
this.userVisibleOnly = true;
setTimeout(this.subscribe, 0);
}
}
});
});
}
public unsubscribe() {
if(!this.isAvailable) {
return;
}
navigator.serviceWorker.ready.then((reg) => {
reg.pushManager.getSubscription().then((subscription) => {
this.isPushEnabled = false;
if(subscription) {
this.pushSubscriptionNotify('unsubscribe', subscription);
setTimeout(() => {
subscription.unsubscribe().then((successful) => {
this.isPushEnabled = false;
}).catch((e) => {
this.log.error('Unsubscription error: ', e);
});
}, 3000);
}
}).catch((e) => {
this.log.error('Error thrown while unsubscribing from ' +
'push messaging.', e);
});
});
}
public forceUnsubscribe() {
if(!this.isAvailable) {
return;
}
navigator.serviceWorker.ready.then((reg) => {
reg.pushManager.getSubscription().then((subscription) => {
this.log.warn('force unsubscribe', subscription);
if(subscription) {
subscription.unsubscribe().then((successful) => {
this.log.warn('force unsubscribe successful', successful);
this.isPushEnabled = false;
}).catch((e) => {
this.log.error('Unsubscription error: ', e);
});
}
}).catch((e) => {
this.log.error('Error thrown while unsubscribing from ' +
'push messaging.', e);
});
});
}
public isAliveNotify = () => {
if(!this.isAvailable || rootScope.idle && rootScope.idle.deactivated) {
return;
}
this.settings.baseUrl = (location.href || '').replace(/#.*$/, '') + '#/im';
const lang: ServiceWorkerPingTask['payload']['lang'] = {} as any;
const ACTIONS_LANG_MAP: Record<keyof ServiceWorkerPingTask['payload']['lang'], LangPackKey> = {
push_action_mute1d: isMobile ? 'PushNotification.Action.Mute1d.Mobile' : 'PushNotification.Action.Mute1d',
push_action_settings: isMobile ? 'PushNotification.Action.Settings.Mobile' : 'PushNotification.Action.Settings',
push_message_nopreview: 'PushNotification.Message.NoPreview'
};
for(const action in ACTIONS_LANG_MAP) {
lang[action as keyof typeof ACTIONS_LANG_MAP] = I18n.format(ACTIONS_LANG_MAP[action as keyof typeof ACTIONS_LANG_MAP], true);
}
const task: ServiceWorkerPingTask = {
type: 'ping',
payload: {
localNotifications: this.localNotificationsAvailable,
lang: lang,
settings: this.settings
}
};
apiManager.postSWMessage(task);
this.isAliveTO = setTimeout(this.isAliveNotify, 10000);
}
public setSettings(newSettings: WebPushApiManager['settings']) {
this.settings = copy(newSettings);
clearTimeout(this.isAliveTO);
this.isAliveNotify();
}
public hidePushNotifications() {
if(!this.isAvailable) {
return;
}
const task: ServiceWorkerNotificationsClearTask = {type: 'notifications_clear'};
apiManager.postSWMessage(task);
}
public setUpServiceWorkerChannel() {
if(!this.isAvailable) {
return;
}
apiManager.addServiceWorkerTaskListener('push_click', (task: ServiceWorkerPushClickTask) => {
if(rootScope.idle && rootScope.idle.deactivated) {
appRuntimeManager.reload();
return;
}
rootScope.dispatchEvent('push_notification_click', task.payload);
});
navigator.serviceWorker.ready.then(this.isAliveNotify);
}
public pushSubscriptionNotify(event: PushSubscriptionNotifyType, subscription?: PushSubscription) {
if(subscription) {
const subscriptionObj: PushSubscriptionJSON = subscription.toJSON();
if(!subscriptionObj ||
!subscriptionObj.endpoint ||
!subscriptionObj.keys ||
!subscriptionObj.keys.p256dh ||
!subscriptionObj.keys.auth) {
this.log.warn('Invalid push subscription', subscriptionObj);
this.unsubscribe();
this.isAvailable = false;
this.pushSubscriptionNotify(event);
return;
}
this.log.warn('Push', event, subscriptionObj);
rootScope.dispatchEvent(('push_' + event) as PushSubscriptionNotifyEvent, {
tokenType: 10,
tokenValue: JSON.stringify(subscriptionObj)
});
} else {
this.log.warn('Push', event, false);
rootScope.dispatchEvent(('push_' + event) as PushSubscriptionNotifyEvent, false as any);
}
}
}
const webPushApiManager = new WebPushApiManager();
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.webPushApiManager = webPushApiManager);
export default webPushApiManager;