console.log('[SW] Push worker started') var pendingNotification = false var defaultBaseUrl switch (location.hostname) { case 'localhost': defaultBaseUrl = 'http://localhost:8000/app/index.html#/im' break case 'zhukov.github.io': defaultBaseUrl = 'https://zhukov.github.io/webogram/#/im' break default: case 'web.telegram.org': defaultBaseUrl = 'https://' + location.hostname + '/#/im' } self.addEventListener('push', function(event) { var obj = event.data.json() console.log('[SW] push', obj) var hasActiveWindows = false var checksPromise = new Promise(function (resolve, reject) { var nowTime = +(new Date()) Promise.all([getMuteUntil(), getLastAliveTime()]).then(function (result) { var muteUntil = result[0] var lastAliveTime = result[1] return clients.matchAll({type: 'window'}).then(function(clientList) { console.log('matched clients', clientList) hasActiveWindows = clientList.length > 0 if (hasActiveWindows) { console.log('Supress notification because some instance is alive') return reject() } if (userInvisibleIsSupported() && muteUntil && nowTime < muteUntil) { console.log('Supress notification because mute for ', Math.ceil((muteUntil - nowTime) / 60000), 'min') return reject() } if (!obj.badge) { return reject() } return resolve() }) }) }) var notificationPromise = checksPromise.then(function () { return Promise.all([getSettings(), getLang()]).then(function (result) { return fireNotification(obj, result[0], result[1]) }) }) var closePromise = notificationPromise.catch(function () { console.log('[SW] Closing all notifications on push', hasActiveWindows) if (userInvisibleIsSupported()) { return closeAllNotifications() } var promise = self.registration.showNotification('Telegram').then(function () { // if (hasActiveWindows) { // return closeAllNotifications() // } setTimeout(closeAllNotifications, hasActiveWindows ? 0 : 100) }).catch(function (error) { console.error('Show notification error', error) }) }) event.waitUntil(closePromise) }) self.addEventListener('install', function(event) { event.waitUntil(self.skipWaiting()); }); self.addEventListener('activate', function(event) { console.log('[SW] on activate') event.waitUntil(self.clients.claim()); }); self.addEventListener('message', function(event) { console.log('[SW] on message', event.data) var client = event.ports && event.ports[0] || event.source if (event.data.type == 'ping') { if (event.data.localNotifications) { lastAliveTime = +(new Date()) IDBManager.setItem('push_last_alive', lastAliveTime) } if (pendingNotification && client && 'postMessage' in client) { client.postMessage(pendingNotification) pendingNotification = false } if (event.data.lang) { lang = event.data.lang IDBManager.setItem('push_lang', lang) } if (event.data.settings) { settings = event.data.settings IDBManager.setItem('push_settings', settings) } } if (event.data.type == 'notifications_clear') { closeAllNotifications() } }) function fireNotification(obj, settings, lang) { var title = obj.title || 'Telegram' var body = obj.description || '' var icon = 'img/logo_share.png' var peerID if (obj.custom && obj.custom.channel_id) { peerID = -obj.custom.channel_id } else if (obj.custom && obj.custom.chat_id) { peerID = -obj.custom.chat_id } else { peerID = obj.custom && obj.custom.from_id || 0 } obj.custom.peerID = peerID var tag = 'peer' + peerID if (settings && settings.nopreview) { title = 'Telegram' body = lang.push_message_nopreview || 'You have a new message' tag = 'unknown_peer' } var notificationPromise = self.registration.showNotification(title, { body: body, icon: icon, tag: tag, data: obj, actions: [ { action: 'mute1d', title: lang.push_action_mute1d || 'Mute for 24H' }, { action: 'push_settings', title: lang.push_action_settings || 'Settings' } ] }) return notificationPromise.then(function (event) { if (event && event.notification) { pushToNotifications(event.notification) } return Promise.resolve() }).catch(function (error) { console.error('Show notification promise', error) }) } var notifications = [] function pushToNotifications(notification) { if (notifications.indexOf(notification) == -1) { notifications.push(notification) notification.onclose = onCloseNotification } } function onCloseNotification(event) { removeFromNotifications(event.notification) } function removeFromNotifications(notification) { var pos = notifications.indexOf(notification) if (pos != -1) { notifications.splice(pos, 1) } } function closeAllNotifications() { for (var i = 0, len = notifications.length; i < len; i++) { try { notifications[i].close() } catch (e) {} } var promise if ('getNotifications' in self.registration) { promise = self.registration.getNotifications({}).then(function(notifications) { for (var i = 0, len = notifications.length; i < len; i++) { try { notifications[i].close() } catch (e) {} } }).catch(function (error) { console.error('Offline register SW error', error) }) } else { promise = Promise.resolve() } notifications = [] return promise } self.addEventListener('notificationclick', function(event) { var notification = event.notification console.log('On notification click: ', notification.tag) notification.close() var action = event.action if (action == 'mute1d' && userInvisibleIsSupported()) { console.log('[SW] mute for 1d') muteUntil = +(new Date()) + 86400000 IDBManager.setItem('push_mute_until', muteUntil) return } if (!notification.data) { return } var promise = clients.matchAll({ type: 'window' }).then(function(clientList) { notification.data.action = action pendingNotification = {type: 'push_click', data: notification.data} for (var i = 0; i < clientList.length; i++) { var client = clientList[i] if ('focus' in client) { client.focus() client.postMessage(pendingNotification) pendingNotification = false return } } if (clients.openWindow) { return getSettings().then(function (settings) { return clients.openWindow(settings.baseUrl || defaultBaseUrl) }) } }).catch(function (error) { console.error('Clients.matchAll error', error) }) event.waitUntil(promise) }) self.addEventListener('notificationclose', onCloseNotification) ;(function () { var dbName = 'keyvalue' var dbStoreName = 'kvItems' var dbVersion = 2 var openDbPromise var idbIsAvailable = self.indexedDB !== undefined && self.IDBTransaction !== undefined function isAvailable () { return idbIsAvailable } function openDatabase () { if (openDbPromise) { return openDbPromise } return openDbPromise = new Promise(function (resolve, reject) { try { var request = indexedDB.open(dbName, dbVersion) var createObjectStore = function (db) { db.createObjectStore(dbStoreName) } if (!request) { return reject() } } catch (error) { console.error('error opening db', error.message) idbIsAvailable = false return reject(error) } var finished = false setTimeout(function () { if (!finished) { request.onerror({type: 'IDB_CREATE_TIMEOUT'}) } }, 3000) request.onsuccess = function (event) { finished = true var db = request.result db.onerror = function (error) { idbIsAvailable = false console.error('Error creating/accessing IndexedDB database', error) reject(error) } resolve(db) } request.onerror = function (event) { finished = true idbIsAvailable = false console.error('Error creating/accessing IndexedDB database', event) reject(event) } request.onupgradeneeded = function (event) { finished = true console.warn('performing idb upgrade from', event.oldVersion, 'to', event.newVersion) var db = event.target.result if (event.oldVersion == 1) { db.deleteObjectStore(dbStoreName) } createObjectStore(db) } }) } function setItem (key, value) { return openDatabase().then(function (db) { try { var objectStore = db.transaction([dbStoreName], IDBTransaction.READ_WRITE || 'readwrite').objectStore(dbStoreName) var request = objectStore.put(value, key) } catch (error) { idbIsAvailable = false return Promise.reject(error) } return new Promise(function(resolve, reject) { request.onsuccess = function (event) { resolve(value) } request.onerror = function (error) { reject(error) } }) }) } function getItem (key) { return openDatabase().then(function (db) { return new Promise(function(resolve, reject) { var objectStore = db.transaction([dbStoreName], IDBTransaction.READ || 'readonly').objectStore(dbStoreName) var request = objectStore.get(key) request.onsuccess = function (event) { var result = event.target.result if (result === undefined) { resolve() } else { resolve(result) } } request.onerror = function (error) { reject(error) } }) }) } openDatabase() self.IDBManager = { name: 'IndexedDB', isAvailable: isAvailable, setItem: setItem, getItem: getItem } })() var lastAliveTime, muteUntil, settings, lang function getMuteUntil() { if (muteUntil !== undefined) { return Promise.resolve(muteUntil) } return IDBManager.getItem('push_mute_until').then(function (newMuteUntil) { return muteUntil = Math.max(muteUntil || 0, newMuteUntil || 0) || false }).catch(function (error) { console.error('IDB error', error) return false }) } function getLastAliveTime() { if (lastAliveTime !== undefined) { return Promise.resolve(lastAliveTime) } return IDBManager.getItem('push_last_alive').then(function (newLastAliveTime) { return lastAliveTime = Math.max(lastAliveTime || 0, newLastAliveTime || 0) || false }).catch(function (error) { console.error('IDB error', error) return false }) } function getLang() { if (lang !== undefined) { return Promise.resolve(lang) } return IDBManager.getItem('push_lang').then(function (newLang) { return lang = newLang || {} }).catch(function (error) { console.error('IDB error', error) return {} }) } function getSettings() { if (settings !== undefined) { return Promise.resolve(settings) } return IDBManager.getItem('push_settings').then(function (newSettings) { return settings = newSettings || {} }).catch(function (error) { console.error('IDB error', error) return {} }) } function userInvisibleIsSupported() { var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1 return isFirefox ? true : false }