/// import { precacheAndRoute } from 'workbox-precaching' import { Server } from 'revolt.js/dist/api/objects' import { Channel, Message, User } from 'revolt.js' import { IDBPDatabase, openDB } from 'idb' import { getItem } from 'localforage' import type { State } from './redux' import { getNotificationState, shouldNotify } from './redux/reducers/notifications' declare let self: ServiceWorkerGlobalScope self.addEventListener('message', (event) => { if (event.data && event.data.type === 'SKIP_WAITING') self.skipWaiting() }) precacheAndRoute(self.__WB_MANIFEST) // ulid decodeTime(id: string) // since crypto is not available in sw.js const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" const ENCODING_LEN = ENCODING.length const TIME_LEN = 10 function decodeTime(id: string) { var time = id.substr(0, TIME_LEN) .split("") .reverse() .reduce(function (carry, char, index) { var encodingIndex = ENCODING.indexOf(char); if (encodingIndex === -1) throw "invalid character found: " + char; return carry += encodingIndex * Math.pow(ENCODING_LEN, index); }, 0); return time; } self.addEventListener("push", event => { async function process() { if (event.data === null) return; let data: Message = event.data.json(); let item = await localStorage.getItem('state'); if (!item) return; const state: State = JSON.parse(item); const autumn_url = state.config.features.autumn.url; const user_id = state.auth.active!; let db: IDBPDatabase; try { // Match RevoltClient.tsx#L55 db = await openDB('state', 3, { upgrade(db) { for (let store of [ "channels", "servers", "users", "members" ]) { db.createObjectStore(store, { keyPath: '_id' }); } }, }); } catch (err) { console.error('Failed to open IndexedDB store, continuing without.'); return; } async function get(store: string, key: string): Promise { try { return await db.get(store, key); } catch (err) { return undefined; } } let channel = await get('channels', data.channel); let user = await get('users', data.author); if (channel) { const notifs = getNotificationState(state.notifications, channel); if (!shouldNotify(notifs, data, user_id)) return; } let title = `@${data.author}`; let username = user?.username ?? data.author; let image; if (data.attachments) { let attachment = data.attachments[0]; if (attachment.metadata.type === "Image") { image = `${autumn_url}/${attachment.tag}/${attachment._id}`; } } switch (channel?.channel_type) { case "SavedMessages": break; case "DirectMessage": title = `@${username}`; break; case "Group": if (user?._id === '00000000000000000000000000') { title = channel.name; } else { title = `@${user?.username} - ${channel.name}`; } break; case "TextChannel": { let server = await get('servers', channel.server); title = `@${user?.username} (#${channel.name}, ${server?.name})`; } break; } await self.registration.showNotification(title, { icon: user?.avatar ? `${autumn_url}/${user.avatar.tag}/${user.avatar._id}` : `https://api.revolt.chat/users/${data.author}/default_avatar`, image, body: typeof data.content === "string" ? data.content : JSON.stringify(data.content), timestamp: decodeTime(data._id), tag: data.channel, badge: "https://app.revolt.chat/assets/icons/android-chrome-512x512.png", data: channel?.channel_type === 'TextChannel' ? `/server/${channel.server}/channel/${channel._id}` : `/channel/${data.channel}` }); } event.waitUntil(process()); }); // ? Open the app on notification click. // https://stackoverflow.com/a/39457287 self.addEventListener("notificationclick", function(event) { let url = event.notification.data; event.notification.close(); event.waitUntil( self.clients .matchAll({ includeUncontrolled: true, type: "window" }) .then(windowClients => { // Check if there is already a window/tab open with the target URL for (var i = 0; i < windowClients.length; i++) { var client = windowClients[i]; // If so, just focus it. if (client.url === url && "focus" in client) { return client.focus(); } } // If not, then open the target URL in a new window/tab. if (self.clients.openWindow) { return self.clients.openWindow(url); } }) ); });