/* * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only */ import { get } from 'idb-keyval'; import * as Misskey from 'misskey-js'; import type { PushNotificationDataMap } from '@/types.js'; import type { I18n } from '@@/js/i18n.js'; import type { Locale } from '../../../locales/index.js'; import { createEmptyNotification, createNotification } from '@/scripts/create-notification.js'; import { swLang } from '@/scripts/lang.js'; import * as swos from '@/scripts/operations.js'; const CACHE_NAME = 'pari-cache-0'; const urlsToCache = [ '/', '/emoji', '/twemoji', '/fluent-emoji', '/vite', ]; globalThis.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME) .then((cache) => cache.addAll(urlsToCache)) ); }); globalThis.addEventListener('activate', (event) => { const cacheWhitelist = [CACHE_NAME, swLang.cacheName]; event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames.map((cacheName) => { if (cacheWhitelist.indexOf(cacheName) === -1) { return caches.delete(cacheName); } }) ); }).then(() => globalThis.clients.claim()) ); }); globalThis.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request) .then((response) => { if (response) { return response; } return fetch(event.request).then( (response) => { if(!response || response.status !== 200 || response.type !== 'basic') { return response; } const responseToCache = response.clone(); caches.open(CACHE_NAME) .then((cache) => { cache.put(event.request, responseToCache); }); return response; } ); }) ); }); async function offlineContentHTML() { const i18n = await (swLang.i18n ?? swLang.fetchLocale()) as Partial>; const messages = { title: i18n.ts?._offlineScreen.title ?? 'Offline - Could not connect to server', header: i18n.ts?._offlineScreen.header ?? 'Could not connect to server', reload: i18n.ts?.reload ?? 'Reload', }; return `${messages.title}
${messages.header}
v${_VERSION_}
`; } globalThis.addEventListener('push', ev => { ev.waitUntil(globalThis.clients.matchAll({ includeUncontrolled: true, type: 'window', }).then(async () => { const data: PushNotificationDataMap[keyof PushNotificationDataMap] = ev.data?.json(); switch (data.type) { case 'notification': case 'unreadAntennaNote': if (Date.now() - data.dateTime > 1000 * 60 * 60 * 24) break; return createNotification(data); case 'readAllNotifications': await globalThis.registration.getNotifications() .then(notifications => notifications.forEach(n => n.tag !== 'read_notification' && n.close())); break; } await createEmptyNotification(); return; })); }); globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEventMap['notificationclick']) => { ev.waitUntil((async (): Promise => { const { action, notification } = ev; const data: PushNotificationDataMap[keyof PushNotificationDataMap] = notification.data ?? {}; const { userId: loginId } = data; let client: WindowClient | null = null; switch (data.type) { case 'notification': switch (action) { case 'follow': if ('userId' in data.body) await swos.api('following/create', loginId, { userId: data.body.userId }); break; case 'showUser': if ('user' in data.body) client = await swos.openUser(Misskey.acct.toString(data.body.user), loginId); break; case 'reply': if ('note' in data.body) client = await swos.openPost({ reply: data.body.note }, loginId); break; case 'renote': if ('note' in data.body) await swos.api('notes/create', loginId, { renoteId: data.body.note.id }); break; case 'accept': if (data.body.type === 'receiveFollowRequest') { await swos.api('following/requests/accept', loginId, { userId: data.body.userId }); } break; case 'reject': if (data.body.type === 'receiveFollowRequest') { await swos.api('following/requests/reject', loginId, { userId: data.body.userId }); } break; case 'showFollowRequests': client = await swos.openClient('push', '/my/follow-requests', loginId); break; default: if (data.body.type === 'receiveFollowRequest') { client = await swos.openClient('push', '/my/follow-requests', loginId); } else if (data.body.type === 'reaction') { client = await swos.openNote(data.body.note.id, loginId); } else if ('note' in data.body) { client = await swos.openNote(data.body.note.id, loginId); } else if ('user' in data.body) { client = await swos.openUser(Misskey.acct.toString(data.body.user), loginId); } break; } break; case 'unreadAntennaNote': client = await swos.openAntenna(data.body.antenna.id, loginId); break; default: if (action === 'markAllAsRead') { await globalThis.registration.getNotifications() .then(notifications => notifications.forEach(n => n.tag !== 'read_notification' && n.close())); await get[]>('accounts').then(accounts => { return Promise.all((accounts ?? []).map(async account => { await swos.sendMarkAllAsRead(account.id); })); }); } else if (action === 'settings') { client = await swos.openClient('push', '/settings/notifications', loginId); } } if (client) { client.focus(); } if (data.type === 'notification') { await swos.sendMarkAllAsRead(loginId); } notification.close(); })()); }); globalThis.addEventListener('notificationclose', (ev: ServiceWorkerGlobalScopeEventMap['notificationclose']) => { const data: PushNotificationDataMap[keyof PushNotificationDataMap] = ev.notification.data; ev.waitUntil((async (): Promise => { if (data.type === 'notification') { await swos.sendMarkAllAsRead(data.userId); } return; })()); }); globalThis.addEventListener('message', (ev: ServiceWorkerGlobalScopeEventMap['message']) => { ev.waitUntil((async (): Promise => { if (ev.data === 'clear') { await caches.keys() .then(cacheNames => Promise.all( cacheNames.map(name => caches.delete(name)), )); return; } if (typeof ev.data === 'object') { const otype = Object.prototype.toString.call(ev.data).slice(8, -1).toLowerCase(); if (otype === 'object') { if (ev.data.msg === 'initialize') { swLang.setLang(ev.data.lang); } } } })()); });