From 8497928e19397e494a6bd0baf0e49c612f38461a Mon Sep 17 00:00:00 2001 From: fly_mc Date: Fri, 22 Nov 2024 00:32:31 +0800 Subject: [PATCH 1/2] Revert "sw: lint" This reverts commit 67d83937246cdcb753355e892283e4d3851d427b. --- packages/sw/src/sw.ts | 563 +++++++++++++++++++++--------------------- 1 file changed, 276 insertions(+), 287 deletions(-) diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts index 7cfd273f5..860a1d191 100644 --- a/packages/sw/src/sw.ts +++ b/packages/sw/src/sw.ts @@ -18,349 +18,338 @@ const STORAGE_QUOTA = 2 * 1024 * 1024 * 1024; // 2GB in bytes const EMOJI_PATH = '/emoji/'; async function requestStorageQuota() { - try { - if (!('storage' in navigator)) { - throw new Error('Storage API not supported'); - } + try { + if (!('storage' in navigator)) { + throw new Error('Storage API not supported'); + } - if ('persist' in navigator) { - const isPersisted = await navigator.storage.persist(); - console.log(`Persisted storage granted: ${isPersisted}`); - } + if ('persist' in navigator) { + const isPersisted = await navigator.storage.persist(); + console.log(`Persisted storage granted: ${isPersisted}`); + } - if ('estimate' in navigator.storage) { - const estimate = await navigator.storage.estimate(); - const currentQuota = estimate.quota || 0; - const currentUsage = estimate.usage || 0; + if ('estimate' in navigator.storage) { + const estimate = await navigator.storage.estimate(); + const currentQuota = estimate.quota || 0; + const currentUsage = estimate.usage || 0; - console.log(`Current storage: ${currentUsage} of ${currentQuota} bytes used`); + console.log(`Current storage: ${currentUsage} of ${currentQuota} bytes used`); - if ('requestQuota' in navigator.storage) { - try { - const grantedQuota = await navigator.storage.requestQuota(STORAGE_QUOTA); - console.log(`Granted quota: ${grantedQuota} bytes`); - } catch (quotaError) { - console.warn('Failed to request additional quota:', quotaError); - } - } + if ('requestQuota' in navigator.storage) { + try { + const grantedQuota = await navigator.storage.requestQuota(STORAGE_QUOTA); + console.log(`Granted quota: ${grantedQuota} bytes`); + } catch (quotaError) { + console.warn('Failed to request additional quota:', quotaError); + } + } - return { - quota: currentQuota, - usage: currentUsage, - }; - } else { - console.warn('Storage estimate API not supported'); - return { - quota: 0, - usage: 0, - }; - } - } catch (error) { - console.error('Failed to request storage quota:', error); - return { - quota: 0, - usage: 0, - }; - } + return { + quota: currentQuota, + usage: currentUsage + }; + } else { + console.warn('Storage estimate API not supported'); + return { + quota: 0, + usage: 0 + }; + } + } catch (error) { + console.error('Failed to request storage quota:', error); + return { + quota: 0, + usage: 0 + }; + } } async function manageStorageSpace(newRequestSize = 0) { - try { - if (!('storage' in navigator)) { - console.warn('Storage API not supported'); - return; - } + try { + if (!('storage' in navigator)) { + console.warn('Storage API not supported'); + return; + } - const estimate = await navigator.storage.estimate(); - const currentUsage = estimate.usage || 0; - const currentQuota = estimate.quota || STORAGE_QUOTA; + const estimate = await navigator.storage.estimate(); + const currentUsage = estimate.usage || 0; + const currentQuota = estimate.quota || STORAGE_QUOTA; - if (currentUsage + newRequestSize > currentQuota) { - console.log(`Storage space needed. Current usage: ${currentUsage}, Need: ${newRequestSize}, Quota: ${currentQuota}`); + if (currentUsage + newRequestSize > currentQuota) { + console.log(`Storage space needed. Current usage: ${currentUsage}, Need: ${newRequestSize}, Quota: ${currentQuota}`); - const cache = await caches.open(STATIC_CACHE_NAME); - const keys = await cache.keys(); + const cache = await caches.open(STATIC_CACHE_NAME); + const keys = await cache.keys(); - const emojiKeys = keys.filter(request => request.url.includes(EMOJI_PATH)); - console.log(`Found ${emojiKeys.length} emoji caches to manage`); + const emojiKeys = keys.filter(request => request.url.includes(EMOJI_PATH)); + console.log(`Found ${emojiKeys.length} emoji caches to manage`); - if (emojiKeys.length > 0) { - for (const key of emojiKeys) { - await cache.delete(key); - console.log(`Deleted cache for: ${key.url}`); + if (emojiKeys.length > 0) { + for (const key of emojiKeys) { + await cache.delete(key); + console.log(`Deleted cache for: ${key.url}`); - const newEstimate = await navigator.storage.estimate(); - const newUsage = newEstimate.usage || 0; + const newEstimate = await navigator.storage.estimate(); + const newUsage = newEstimate.usage || 0; - if (newUsage + newRequestSize <= currentQuota) { - console.log(`Sufficient space cleared. New usage: ${newUsage}`); - break; - } - } - } else { - console.warn('No emoji caches available for cleanup'); - } - } - } catch (error) { - console.error('Failed to manage storage space:', error); - } + if (newUsage + newRequestSize <= currentQuota) { + console.log(`Sufficient space cleared. New usage: ${newUsage}`); + break; + } + } + } else { + console.warn('No emoji caches available for cleanup'); + } + } + } catch (error) { + console.error('Failed to manage storage space:', error); + } } async function cacheWithFallback(cache, paths) { - for (const path of paths) { - try { - const response = await fetch(new Request(path, { credentials: 'same-origin' })); - const blob = await response.clone().blob(); + for (const path of paths) { + try { + const response = await fetch(new Request(path, { credentials: 'same-origin' })); + const blob = await response.clone().blob(); - await manageStorageSpace(blob.size); + await manageStorageSpace(blob.size); - await cache.put(new Request(path, { credentials: 'same-origin' }), response); - } catch (error) { - console.error(`Failed to cache ${path}:`, error); - } - } + await cache.put(new Request(path, { credentials: 'same-origin' }), response); + } catch (error) { + console.error(`Failed to cache ${path}:`, error); + } + } } globalThis.addEventListener('install', (ev) => { - ev.waitUntil((async () => { - await requestStorageQuota(); + ev.waitUntil((async () => { + await requestStorageQuota(); - const cache = await caches.open(STATIC_CACHE_NAME); - await cacheWithFallback(cache, PATHS_TO_CACHE); - await globalThis.skipWaiting(); - })()); + const cache = await caches.open(STATIC_CACHE_NAME); + await cacheWithFallback(cache, PATHS_TO_CACHE); + await globalThis.skipWaiting(); + })()); }); -globalThis.addEventListener('activate', (ev) => { - ev.waitUntil( - caches.keys() - .then(cacheNames => Promise.all( - cacheNames - .filter((v) => v !== STATIC_CACHE_NAME && v !== swLang.cacheName) - .map(name => caches.delete(name)), - )) - .then(() => globalThis.clients.claim()), - ); +globalThis.addEventListener('activate', ev => { + ev.waitUntil( + caches.keys() + .then(cacheNames => Promise.all( + cacheNames + .filter((v) => v !== STATIC_CACHE_NAME && v !== swLang.cacheName) + .map(name => caches.delete(name)), + )) + .then(() => globalThis.clients.claim()), + ); }); 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', - }; + 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_}
`; + return `${messages.title}
${messages.header}
v${_VERSION_}
`; } -globalThis.addEventListener('fetch', (ev) => { - const shouldCache = PATHS_TO_CACHE.some(path => ev.request.url.includes(path)); +globalThis.addEventListener('fetch', ev => { + const shouldCache = PATHS_TO_CACHE.some(path => ev.request.url.includes(path)); - if (shouldCache) { + if (shouldCache) { + ev.respondWith( + caches.match(ev.request) + .then(async response => { + if (response) return response; + + const fetchResponse = await fetch(ev.request); + if (!fetchResponse || fetchResponse.status !== 200 || fetchResponse.type !== 'basic') { + return fetchResponse; + } + + const blob = await fetchResponse.clone().blob(); + await manageStorageSpace(blob.size); + + const responseToCache = fetchResponse.clone(); + const cache = await caches.open(STATIC_CACHE_NAME); + await cache.put(ev.request, responseToCache); + + return fetchResponse; + }) + ); + return; + } + + 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 (!isHTMLRequest) return; ev.respondWith( - caches.match(ev.request) - .then(async (response) => { - if (response) return response; - - const fetchResponse = await fetch(ev.request); - if (!fetchResponse || fetchResponse.status !== 200 || fetchResponse.type !== 'basic') { - return fetchResponse; - } - - const blob = await fetchResponse.clone().blob(); - await manageStorageSpace(blob.size); - - const responseToCache = fetchResponse.clone(); - const cache = await caches.open(STATIC_CACHE_NAME); - await cache.put(ev.request, responseToCache); - - return fetchResponse; - }), + fetch(ev.request) + .catch(async () => { + const html = await offlineContentHTML(); + return new Response(html, { + status: 200, + headers: { + 'content-type': 'text/html', + }, + }); + }), ); - return; - } - - 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('index.html')) { - isHTMLRequest = true; - } - - if (isHTMLRequest) { - ev.respondWith( - caches.match(ev.request) - .then(async (response) => { - if (response) return response; - const offlineHTML = await offlineContentHTML(); - return new Response(offlineHTML, { - headers: { 'Content-Type': 'text/html' }, - }); - }), - ); - } }); -globalThis.addEventListener('push', (ev) => { - ev.waitUntil( - globalThis.clients.matchAll({ - includeUncontrolled: true, - type: 'window', +globalThis.addEventListener('push', ev => { + ev.waitUntil(globalThis.clients.matchAll({ + includeUncontrolled: true, + type: 'window', }).then(async () => { - const data: PushNotificationDataMap[keyof PushNotificationDataMap] = ev.data?.json(); + 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; + switch (data.type) { + case 'notification': + case 'unreadAntennaNote': + if (Date.now() - data.dateTime > 1000 * 60 * 60 * 24) break; - return createNotification(data); + return createNotification(data); + case 'readAllNotifications': + await globalThis.registration.getNotifications() + .then(notifications => notifications.forEach(n => n.tag !== 'read_notification' && n.close())); + break; + } - case 'readAllNotifications': - await globalThis.registration.getNotifications() - .then(notifications => { - notifications.forEach(n => { - if (n.tag !== 'read_notification') n.close(); - }); - }); - break; - } - - await createEmptyNotification(); - }), - ); + await createEmptyNotification(); + return; + })); }); 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; - 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; + ev.waitUntil((async (): Promise => { + if (_DEV_) { + console.log('notificationclick', ev.action, ev.notification.data); } - break; - case 'unreadAntennaNote': - client = await swos.openAntenna(data.body.antenna.id, loginId); - break; + const { action, notification } = ev; + const data: PushNotificationDataMap[keyof PushNotificationDataMap] = notification.data ?? {}; + const { userId: loginId } = data; + let client: WindowClient | null = null; - default: - switch (action) { - case 'markAllAsRead': - await globalThis.registration.getNotifications() - .then(notifications => notifications.forEach(n => { - if (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; + 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': + switch (data.body.type) { + case 'receiveFollowRequest': + await swos.api('following/requests/accept', loginId, { userId: data.body.userId }); + break; + } + break; + case 'reject': + 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: + 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; + case 'unreadAntennaNote': + client = await swos.openAntenna(data.body.antenna.id, loginId); + break; + default: + 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; + } } - } - if (client) { - client.focus(); - } + if (client) { + client.focus(); + } + if (data.type === 'notification') { + await swos.sendMarkAllAsRead(loginId); + } - if (data.type === 'notification') { - await swos.sendMarkAllAsRead(loginId); - } - - notification.close(); - })()); + notification.close(); + })()); }); globalThis.addEventListener('notificationclose', (ev: ServiceWorkerGlobalScopeEventMap['notificationclose']) => { - const data: PushNotificationDataMap[keyof PushNotificationDataMap] = ev.notification.data; + const data: PushNotificationDataMap[keyof PushNotificationDataMap] = ev.notification.data; - ev.waitUntil((async (): Promise => { - if (data.type === 'notification') { - await swos.sendMarkAllAsRead(data.userId); - } - })()); + 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); + 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); + } + } + } + })()); }); From 8356623c77d561c08aade4fa259dd891cf74db0a Mon Sep 17 00:00:00 2001 From: fly_mc Date: Fri, 22 Nov 2024 00:32:39 +0800 Subject: [PATCH 2/2] Revert "sw: use StorageAPI and better cache management" This reverts commit 48683b555755a2620869b0477e0743543eeb7c50. --- packages/sw/src/sw.ts | 165 ++++++++---------------------------------- 1 file changed, 32 insertions(+), 133 deletions(-) diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts index 860a1d191..458b2081d 100644 --- a/packages/sw/src/sw.ts +++ b/packages/sw/src/sw.ts @@ -14,121 +14,21 @@ import * as swos from '@/scripts/operations.js'; const STATIC_CACHE_NAME = `misskey-static-${_VERSION_}`; const PATHS_TO_CACHE = ['/assets/', '/static-assets/', '/emoji/', '/twemoji/', '/fluent-emoji/', '/vite/']; -const STORAGE_QUOTA = 2 * 1024 * 1024 * 1024; // 2GB in bytes -const EMOJI_PATH = '/emoji/'; - -async function requestStorageQuota() { - try { - if (!('storage' in navigator)) { - throw new Error('Storage API not supported'); - } - - if ('persist' in navigator) { - const isPersisted = await navigator.storage.persist(); - console.log(`Persisted storage granted: ${isPersisted}`); - } - - if ('estimate' in navigator.storage) { - const estimate = await navigator.storage.estimate(); - const currentQuota = estimate.quota || 0; - const currentUsage = estimate.usage || 0; - - console.log(`Current storage: ${currentUsage} of ${currentQuota} bytes used`); - - if ('requestQuota' in navigator.storage) { - try { - const grantedQuota = await navigator.storage.requestQuota(STORAGE_QUOTA); - console.log(`Granted quota: ${grantedQuota} bytes`); - } catch (quotaError) { - console.warn('Failed to request additional quota:', quotaError); - } - } - - return { - quota: currentQuota, - usage: currentUsage - }; - } else { - console.warn('Storage estimate API not supported'); - return { - quota: 0, - usage: 0 - }; - } - } catch (error) { - console.error('Failed to request storage quota:', error); - return { - quota: 0, - usage: 0 - }; - } -} - -async function manageStorageSpace(newRequestSize = 0) { - try { - if (!('storage' in navigator)) { - console.warn('Storage API not supported'); - return; - } - - const estimate = await navigator.storage.estimate(); - const currentUsage = estimate.usage || 0; - const currentQuota = estimate.quota || STORAGE_QUOTA; - - if (currentUsage + newRequestSize > currentQuota) { - console.log(`Storage space needed. Current usage: ${currentUsage}, Need: ${newRequestSize}, Quota: ${currentQuota}`); - - const cache = await caches.open(STATIC_CACHE_NAME); - const keys = await cache.keys(); - - const emojiKeys = keys.filter(request => request.url.includes(EMOJI_PATH)); - console.log(`Found ${emojiKeys.length} emoji caches to manage`); - - if (emojiKeys.length > 0) { - for (const key of emojiKeys) { - await cache.delete(key); - console.log(`Deleted cache for: ${key.url}`); - - const newEstimate = await navigator.storage.estimate(); - const newUsage = newEstimate.usage || 0; - - if (newUsage + newRequestSize <= currentQuota) { - console.log(`Sufficient space cleared. New usage: ${newUsage}`); - break; - } - } - } else { - console.warn('No emoji caches available for cleanup'); - } - } - } catch (error) { - console.error('Failed to manage storage space:', error); - } -} async function cacheWithFallback(cache, paths) { - for (const path of paths) { - try { - const response = await fetch(new Request(path, { credentials: 'same-origin' })); - const blob = await response.clone().blob(); - - await manageStorageSpace(blob.size); - - await cache.put(new Request(path, { credentials: 'same-origin' }), response); - } catch (error) { - console.error(`Failed to cache ${path}:`, error); - } - } + for (const path of paths) { + try { + await cache.add(new Request(path, { credentials: 'same-origin' })); + } catch (error) {} + } } globalThis.addEventListener('install', (ev) => { - ev.waitUntil((async () => { - await requestStorageQuota(); - - const cache = await caches.open(STATIC_CACHE_NAME); - await cacheWithFallback(cache, PATHS_TO_CACHE); - await globalThis.skipWaiting(); - })()); + ev.waitUntil((async () => { + const cache = await caches.open(STATIC_CACHE_NAME); + await cacheWithFallback(cache, PATHS_TO_CACHE); + await globalThis.skipWaiting(); + })()); }); globalThis.addEventListener('activate', ev => { @@ -155,31 +55,27 @@ async function offlineContentHTML() { } globalThis.addEventListener('fetch', ev => { - const shouldCache = PATHS_TO_CACHE.some(path => ev.request.url.includes(path)); + const shouldCache = PATHS_TO_CACHE.some(path => ev.request.url.includes(path)); - if (shouldCache) { - ev.respondWith( - caches.match(ev.request) - .then(async response => { - if (response) return response; + if (shouldCache) { + ev.respondWith( + caches.match(ev.request) + .then(response => { + if (response) return response; - const fetchResponse = await fetch(ev.request); - if (!fetchResponse || fetchResponse.status !== 200 || fetchResponse.type !== 'basic') { - return fetchResponse; - } - - const blob = await fetchResponse.clone().blob(); - await manageStorageSpace(blob.size); - - const responseToCache = fetchResponse.clone(); - const cache = await caches.open(STATIC_CACHE_NAME); - await cache.put(ev.request, responseToCache); - - return fetchResponse; - }) - ); - return; - } + return fetch(ev.request).then(response => { + if (!response || response.status !== 200 || response.type !== 'basic') return response; + const responseToCache = response.clone(); + caches.open(STATIC_CACHE_NAME) + .then(cache => { + cache.put(ev.request, responseToCache); + }); + return response; + }); + }) + ); + return; + } let isHTMLRequest = false; if (ev.request.headers.get('sec-fetch-dest') === 'document') { @@ -272,6 +168,9 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv case 'showFollowRequests': client = await swos.openClient('push', '/my/follow-requests', loginId); break; + case 'edited': + if ('note' in data.body) client = await swos.openPost({ reply: data.body.note }, loginId); + break; default: switch (data.body.type) { case 'receiveFollowRequest':