diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts
index a2d786df7..8cd271252 100644
--- a/packages/sw/src/sw.ts
+++ b/packages/sw/src/sw.ts
@@ -1,8 +1,3 @@
-/*
- * 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';
@@ -12,99 +7,37 @@ import { createEmptyNotification, createNotification } from '@/scripts/create-no
import { swLang } from '@/scripts/lang.js';
import * as swos from '@/scripts/operations.js';
-const CACHE_NAME = 'pari-cache-${_VERSION_}';
-const urlsToCache = [
- '/manifest.json',
+const STATIC_ASSETS = [
'/assets',
'/emoji',
'/twemoji',
'/fluent-emoji',
'/vite',
- '/identicon',
- '/proxy'
];
-globalThis.addEventListener('install', (event) => {
- event.waitUntil(
- caches.open(CACHE_NAME)
- .then((cache) => cache.addAll(urlsToCache))
+const STATIC_CACHE_NAME = `static-cache-${_VERSION_}`;
+const LANG_CACHE_NAME = swLang.cacheName;
+
+globalThis.addEventListener('install', (ev) => {
+ ev.waitUntil(
+ (async () => {
+ const cache = await caches.open(STATIC_CACHE_NAME);
+ await cache.addAll(STATIC_ASSETS);
+ })(),
);
});
-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) => {
- let isHTMLRequest = false;
-
- if (event.request.headers.get('sec-fetch-dest') === 'document' ||
- event.request.headers.get('accept')?.includes('/html') ||
- event.request.url.endsWith('/')) {
- isHTMLRequest = true;
- }
-
- if (isHTMLRequest) {
- event.respondWith(
- fetch(event.request)
- .catch(async () => {
- const html = await offlineContentHTML();
- return new Response(html, {
- status: 200,
- headers: {
- 'content-type': 'text/html',
- },
- });
- })
- );
- return;
- }
-
- 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;
- }
- ).catch(async () => {
- if (isHTMLRequest) {
- const html = await offlineContentHTML();
- return new Response(html, {
- status: 200,
- headers: {
- 'content-type': 'text/html',
- },
- });
- }
- throw new Error('Failed to fetch');
- });
- })
+globalThis.addEventListener('activate', ev => {
+ ev.waitUntil(
+ (async () => {
+ const cacheNames = await caches.keys();
+ const deletePromises = cacheNames
+ .filter(name => name !== STATIC_CACHE_NAME && name !== LANG_CACHE_NAME)
+ .map(name => caches.delete(name));
+
+ await Promise.all(deletePromises);
+ await globalThis.clients.claim();
+ })(),
);
});
@@ -119,6 +52,70 @@ async function offlineContentHTML() {
return `
${messages.title}${messages.header}
v${_VERSION_}
`;
}
+globalThis.addEventListener('fetch', ev => {
+ const url = new URL(ev.request.url);
+
+ const isStaticAsset = STATIC_ASSETS.some(path => url.pathname.startsWith(path));
+
+ let isHTMLRequest = false;
+ if (ev.request.headers.get('sec-fetch-dest') === 'document') {
+ isHTMLRequest = true;
+ } else if (ev.request.headers.get('accept')?.includes('/html')) {
+ isHTMLRequest = true;
+ } else if (ev.request.url.endsWith('/')) {
+ isHTMLRequest = true;
+ }
+
+ if (isStaticAsset) {
+ ev.respondWith(
+ caches.match(ev.request)
+ .then(response => {
+ if (response) {
+ return response;
+ }
+ return fetch(ev.request).then(response => {
+ if (response.status === 200) {
+ const responseClone = response.clone();
+ caches.open(STATIC_CACHE_NAME).then(cache => {
+ cache.put(ev.request, responseClone);
+ });
+ }
+ return response;
+ });
+ })
+ .catch(() => {
+ if (isHTMLRequest) {
+ return offlineContentHTML().then(html => {
+ return new Response(html, {
+ status: 200,
+ headers: {
+ 'content-type': 'text/html',
+ },
+ });
+ });
+ }
+ }),
+ );
+ return;
+ }
+
+ if (isHTMLRequest) {
+ ev.respondWith(
+ fetch(ev.request)
+ .catch(async () => {
+ const html = await offlineContentHTML();
+ return new Response(html, {
+ status: 200,
+ headers: {
+ 'content-type': 'text/html',
+ },
+ });
+ }),
+ );
+ return;
+ }
+});
+
globalThis.addEventListener('push', ev => {
ev.waitUntil(globalThis.clients.matchAll({
includeUncontrolled: true,
@@ -144,6 +141,10 @@ globalThis.addEventListener('push', ev => {
globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEventMap['notificationclick']) => {
ev.waitUntil((async (): Promise => {
+ if (_DEV_) {
+ console.log('notificationclick', ev.action, ev.notification.data);
+ }
+
const { action, notification } = ev;
const data: PushNotificationDataMap[keyof PushNotificationDataMap] = notification.data ?? {};
const { userId: loginId } = data;
@@ -165,45 +166,57 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv
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 });
+ switch (data.body.type) {
+ case 'receiveFollowRequest':
+ await swos.api('following/requests/accept', loginId, { userId: data.body.userId });
+ break;
}
break;
case 'reject':
- if (data.body.type === 'receiveFollowRequest') {
- await swos.api('following/requests/reject', loginId, { userId: data.body.userId });
+ switch (data.body.type) {
+ case 'receiveFollowRequest':
+ await swos.api('following/requests/reject', loginId, { userId: data.body.userId });
+ break;
}
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);
+ switch (data.body.type) {
+ case 'receiveFollowRequest':
+ client = await swos.openClient('push', '/my/follow-requests', loginId);
+ break;
+ case 'reaction':
+ client = await swos.openNote(data.body.note.id, loginId);
+ break;
+ default:
+ 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;
}
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);
+ switch (action) {
+ case '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);
+ }));
+ });
+ break;
+ case 'settings':
+ client = await swos.openClient('push', '/settings/notifications', loginId);
+ break;
}
}
@@ -231,12 +244,13 @@ globalThis.addEventListener('notificationclose', (ev: ServiceWorkerGlobalScopeEv
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;
+ switch (ev.data) {
+ case 'clear':
+ await caches.keys()
+ .then(cacheNames => Promise.all(
+ cacheNames.map(name => caches.delete(name)),
+ ));
+ return;
}
if (typeof ev.data === 'object') {