declare let self: ServiceWorkerGlobalScope; 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 ( 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", ( 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", ( 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); } } } })(), ); }, );