cfd7cf0f1e
* Typecheck the service worker Currently the service worker build never typechecks, since esbuild and typescript-eslint don't do such job. esbuild: https://esbuild.github.io/content-types/#typescript >However, esbuild does not do any type checking so you will still need to run tsc -noEmit in parallel with esbuild to check types. This is not something esbuild does itself. typescript-eslint: https://typescript-eslint.io/linting/troubleshooting#why-dont-i-see-typescript-errors-in-my-eslint-output >TypeScript's compiler (or whatever your build chain may be) is specifically designed and built to validate the correctness of your codebase. Our tooling does not reproduce the errors that TypeScript provides, because doing so would slow down the lint run [1], and duplicate the errors that TypeScript already outputs for you. Adding this step adds tons of TS errors 😱 * Override lib-webworker with service worker
212 lines
6.4 KiB
TypeScript
212 lines
6.4 KiB
TypeScript
import { createEmptyNotification, createNotification } from '@/scripts/create-notification';
|
|
import { swLang } from '@/scripts/lang';
|
|
import { swNotificationRead } from '@/scripts/notification-read';
|
|
import { pushNotificationDataMap } from '@/types';
|
|
import * as swos from '@/scripts/operations';
|
|
import { acct as getAcct } from '@/filters/user';
|
|
|
|
self.addEventListener('install', ev => {
|
|
ev.waitUntil(self.skipWaiting());
|
|
});
|
|
|
|
self.addEventListener('activate', ev => {
|
|
ev.waitUntil(
|
|
caches.keys()
|
|
.then(cacheNames => Promise.all(
|
|
cacheNames
|
|
.filter((v) => v !== swLang.cacheName)
|
|
.map(name => caches.delete(name))
|
|
))
|
|
.then(() => self.clients.claim())
|
|
);
|
|
});
|
|
|
|
self.addEventListener('fetch', ev => {
|
|
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(
|
|
fetch(ev.request)
|
|
.catch(() => new Response(`Offline. Service Worker @${_VERSION_}`, { status: 200 }))
|
|
);
|
|
});
|
|
|
|
self.addEventListener('push', ev => {
|
|
// クライアント取得
|
|
ev.waitUntil(self.clients.matchAll({
|
|
includeUncontrolled: true,
|
|
type: 'window'
|
|
}).then(async <K extends keyof pushNotificationDataMap>(clients: readonly WindowClient[]) => {
|
|
const data: pushNotificationDataMap[K] = ev.data?.json();
|
|
|
|
switch (data.type) {
|
|
// case 'driveFileCreated':
|
|
case 'notification':
|
|
case 'unreadMessagingMessage':
|
|
// 1日以上経過している場合は無視
|
|
if ((new Date()).getTime() - data.dateTime > 1000 * 60 * 60 * 24) break;
|
|
|
|
// クライアントがあったらストリームに接続しているということなので通知しない
|
|
if (clients.length !== 0) break;
|
|
|
|
return createNotification(data);
|
|
case 'readAllNotifications':
|
|
for (const n of await self.registration.getNotifications()) {
|
|
if (n?.data?.type === 'notification') n.close();
|
|
}
|
|
break;
|
|
case 'readAllMessagingMessages':
|
|
for (const n of await self.registration.getNotifications()) {
|
|
if (n?.data?.type === 'unreadMessagingMessage') n.close();
|
|
}
|
|
break;
|
|
case 'readNotifications':
|
|
for (const n of await self.registration.getNotifications()) {
|
|
if (data.body?.notificationIds?.includes(n.data.body.id)) {
|
|
n.close();
|
|
}
|
|
}
|
|
break;
|
|
case 'readAllMessagingMessagesOfARoom':
|
|
for (const n of await self.registration.getNotifications()) {
|
|
if (n.data.type === 'unreadMessagingMessage'
|
|
&& ('userId' in data.body
|
|
? data.body.userId === n.data.body.userId
|
|
: data.body.groupId === n.data.body.groupId)
|
|
) {
|
|
n.close();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return createEmptyNotification();
|
|
}));
|
|
});
|
|
|
|
self.addEventListener('notificationclick', <K extends keyof pushNotificationDataMap>(ev: ServiceWorkerGlobalScopeEventMap['notificationclick']) => {
|
|
ev.waitUntil((async () => {
|
|
if (_DEV_) {
|
|
console.log('notificationclick', ev.action, ev.notification.data);
|
|
}
|
|
|
|
const { action, notification } = ev;
|
|
const data: pushNotificationDataMap[K] = notification.data;
|
|
const { userId: id } = 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', id, { userId: data.body.userId });
|
|
break;
|
|
case 'showUser':
|
|
if ('user' in data.body) client = await swos.openUser(getAcct(data.body.user), id);
|
|
break;
|
|
case 'reply':
|
|
if ('note' in data.body) client = await swos.openPost({ reply: data.body.note }, id);
|
|
break;
|
|
case 'renote':
|
|
if ('note' in data.body) await swos.api('notes/create', id, { renoteId: data.body.note.id });
|
|
break;
|
|
case 'accept':
|
|
switch (data.body.type) {
|
|
case 'receiveFollowRequest':
|
|
await swos.api('following/requests/accept', id, { userId: data.body.userId });
|
|
break;
|
|
case 'groupInvited':
|
|
await swos.api('users/groups/invitations/accept', id, { invitationId: data.body.invitation.id });
|
|
break;
|
|
}
|
|
break;
|
|
case 'reject':
|
|
switch (data.body.type) {
|
|
case 'receiveFollowRequest':
|
|
await swos.api('following/requests/reject', id, { userId: data.body.userId });
|
|
break;
|
|
case 'groupInvited':
|
|
await swos.api('users/groups/invitations/reject', id, { invitationId: data.body.invitation.id });
|
|
break;
|
|
}
|
|
break;
|
|
case 'showFollowRequests':
|
|
client = await swos.openClient('push', '/my/follow-requests', id);
|
|
break;
|
|
default:
|
|
switch (data.body.type) {
|
|
case 'receiveFollowRequest':
|
|
client = await swos.openClient('push', '/my/follow-requests', id);
|
|
break;
|
|
case 'groupInvited':
|
|
client = await swos.openClient('push', '/my/groups', id);
|
|
break;
|
|
case 'reaction':
|
|
client = await swos.openNote(data.body.note.id, id);
|
|
break;
|
|
default:
|
|
if ('note' in data.body) {
|
|
client = await swos.openNote(data.body.note.id, id);
|
|
} else if ('user' in data.body) {
|
|
client = await swos.openUser(getAcct(data.body.user), id);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case 'unreadMessagingMessage':
|
|
client = await swos.openChat(data.body, id);
|
|
break;
|
|
}
|
|
|
|
if (client) {
|
|
client.focus();
|
|
}
|
|
if (data.type === 'notification') {
|
|
swNotificationRead.then(that => that.read(data));
|
|
}
|
|
|
|
notification.close();
|
|
})());
|
|
});
|
|
|
|
self.addEventListener('notificationclose', <K extends keyof pushNotificationDataMap>(ev: ServiceWorkerGlobalScopeEventMap['notificationclose']) => {
|
|
const data: pushNotificationDataMap[K] = ev.notification.data;
|
|
|
|
if (data.type === 'notification') {
|
|
swNotificationRead.then(that => that.read(data));
|
|
}
|
|
});
|
|
|
|
self.addEventListener('message', (ev: ServiceWorkerGlobalScopeEventMap['message']) => {
|
|
ev.waitUntil((async () => {
|
|
switch (ev.data) {
|
|
case 'clear':
|
|
// Cache Storage全削除
|
|
await caches.keys()
|
|
.then(cacheNames => Promise.all(
|
|
cacheNames.map(name => caches.delete(name))
|
|
));
|
|
return; // TODO
|
|
}
|
|
|
|
if (typeof ev.data === 'object') {
|
|
// E.g. '[object Array]' → 'array'
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
})());
|
|
});
|