227 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			227 lines
		
	
	
		
			7.0 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':
 | |
| 			case 'unreadAntennaNote':
 | |
| 				// 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 'readAllAntennas':
 | |
| 				for (const n of await self.registration.getNotifications()) {
 | |
| 					if (n?.data?.type === 'unreadAntennaNote') 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;
 | |
| 			case 'readAntenna':
 | |
| 				for (const n of await self.registration.getNotifications()) {
 | |
| 					if (n?.data?.type === 'unreadAntennaNote' && data.body?.antennaId === n.data.body.antenna.id) {
 | |
| 						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: 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(getAcct(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;
 | |
| 							case 'groupInvited':
 | |
| 								await swos.api('users/groups/invitations/accept', loginId, { invitationId: data.body.invitation.id });
 | |
| 								break;
 | |
| 						}
 | |
| 						break;
 | |
| 					case 'reject':
 | |
| 						switch (data.body.type) {
 | |
| 							case 'receiveFollowRequest':
 | |
| 								await swos.api('following/requests/reject', loginId, { userId: data.body.userId });
 | |
| 								break;
 | |
| 							case 'groupInvited':
 | |
| 								await swos.api('users/groups/invitations/reject', loginId, { invitationId: data.body.invitation.id });
 | |
| 								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 'groupInvited':
 | |
| 								client = await swos.openClient('push', '/my/groups', 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(getAcct(data.body.user), loginId);
 | |
| 								}
 | |
| 								break;
 | |
| 						}
 | |
| 				}
 | |
| 				break;
 | |
| 			case 'unreadMessagingMessage':
 | |
| 				client = await swos.openChat(data.body, loginId);
 | |
| 				break;
 | |
| 			case 'unreadAntennaNote':
 | |
| 				client = await swos.openAntenna(data.body.antenna.id, loginId);
 | |
| 		}
 | |
| 	
 | |
| 		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);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	})());
 | |
| });
 | 
