Revert "Fix notification (#2654)"

This reverts commit 09cb1bba713072e075858a24eee2712076793dde.
This commit is contained in:
ThinaticSystem 2022-07-09 18:13:20 +09:00
parent d03e9a4da1
commit 9ebed95fb6
7 changed files with 301 additions and 21 deletions

View File

@ -38,8 +38,10 @@ export async function readNotificationByQuery(
function postReadAllNotifications(userId: User['id']) {
publishMainStream(userId, 'readAllNotifications');
return pushNotification(userId, 'readAllNotifications', undefined);
}
function postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) {
publishMainStream(userId, 'readNotifications', notificationIds);
return pushNotification(userId, 'readNotifications', { notificationIds });
}

View File

@ -29,4 +29,5 @@ export default define(meta, paramDef, async (ps, user) => {
// 全ての通知を読みましたよというイベントを発行
publishMainStream(user.id, 'readAllNotifications');
pushNotification(user.id, 'readAllNotifications', undefined);
});

View File

@ -20,14 +20,30 @@ export const meta = {
} as const;
export const paramDef = {
oneOf: [
{
type: 'object',
properties: {
notificationId: { type: 'string', format: 'misskey:id' },
},
required: ['notificationId'],
},
{
type: 'object',
properties: {
notificationIds: {
type: 'array',
items: { type: 'string', format: 'misskey:id' },
maxItems: 100,
},
},
required: ['notificationIds'],
},
],
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
return readNotification(user.id, [ps.notificationId]);
if ('notificationId' in ps) return readNotification(user.id, [ps.notificationId]);
return readNotification(user.id, ps.notificationIds);
});

View File

@ -60,8 +60,6 @@ export async function pushNotification<T extends keyof pushNotificationsTypes>(u
},
};
//console.log(`send: ${subscription.endpoint?.substring(0, 64)}`);
//console.log(`send: ${type} ${JSON.stringify(body)}`);
push.sendNotification(pushSubscription, JSON.stringify({
type,
body: type === 'notification' ? truncateNotification(body as Packed<'Notification'>) : body,
@ -69,9 +67,9 @@ export async function pushNotification<T extends keyof pushNotificationsTypes>(u
}), {
proxy: config.proxy,
}).catch((err: any) => {
//console.log(err.statusCode);
//console.log(err.headers);
//console.log(err.body);
//swLogger.info(err.statusCode);
//swLogger.info(err.headers);
//swLogger.info(err.body);
if (err.statusCode === 410) {
SwSubscriptions.delete({

View File

@ -21,6 +21,7 @@ export async function createNotification<K extends keyof pushNotificationDataMap
return self.registration.showNotification(...n);
} else {
console.error('Could not compose notification', data);
return createEmptyNotification();
}
}
@ -43,11 +44,18 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
// users/showの型定義をswos.apiへ当てはめるのが困難なのでapiFetch.requestを直接使用
const account = await getAccountFromId(data.userId);
if (!account) return null;
const userDetail = await cli.request('users/show', { userId: data.body.userId }, account.token);
return [t('_notification.youWereFollowed'), {
body: getUserName(data.body.user),
icon: data.body.user.avatarUrl,
badge: iconUrl('user-plus'),
data,
actions: userDetail.isFollowing ? [] : [
{
action: 'follow',
title: t('_notification._actions.followBack')
}
],
}];
case 'mention':
@ -56,6 +64,12 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
icon: data.body.user.avatarUrl,
badge: iconUrl('at'),
data,
actions: [
{
action: 'reply',
title: t('_notification._actions.reply')
}
],
}];
case 'reply':
@ -64,6 +78,12 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
icon: data.body.user.avatarUrl,
badge: iconUrl('reply'),
data,
actions: [
{
action: 'reply',
title: t('_notification._actions.reply')
}
],
}];
case 'renote':
@ -72,6 +92,12 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
icon: data.body.user.avatarUrl,
badge: iconUrl('retweet'),
data,
actions: [
{
action: 'showUser',
title: getUserName(data.body.user)
}
],
}];
case 'quote':
@ -80,6 +106,18 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
icon: data.body.user.avatarUrl,
badge: iconUrl('quote-right'),
data,
actions: [
{
action: 'reply',
title: t('_notification._actions.reply')
},
...((data.body.note.visibility === 'public' || data.body.note.visibility === 'home') ? [
{
action: 'renote',
title: t('_notification._actions.renote')
}
] : [])
],
}];
case 'reaction':
@ -122,6 +160,12 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
icon: data.body.user.avatarUrl,
badge,
data,
actions: [
{
action: 'showUser',
title: getUserName(data.body.user)
}
],
}];
case 'pollVote':
@ -145,6 +189,16 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
icon: data.body.user.avatarUrl,
badge: iconUrl('clock'),
data,
actions: [
{
action: 'accept',
title: t('accept')
},
{
action: 'reject',
title: t('reject')
}
],
}];
case 'followRequestAccepted':
@ -160,6 +214,16 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
body: data.body.invitation.group.name,
badge: iconUrl('id-card-alt'),
data,
actions: [
{
action: 'accept',
title: t('accept')
},
{
action: 'reject',
title: t('reject')
}
],
}];
case 'app':
@ -193,3 +257,33 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
return null;
}
}
export async function createEmptyNotification() {
return new Promise<void>(async res => {
if (!swLang.i18n) swLang.fetchLocale();
const i18n = await swLang.i18n as I18n<any>;
const { t } = i18n;
await self.registration.showNotification(
t('_notification.emptyPushNotificationMessage'),
{
silent: true,
badge: iconUrl('null'),
tag: 'read_notification',
}
);
res();
setTimeout(async () => {
for (const n of
[
...(await self.registration.getNotifications({ tag: 'user_visible_auto_notification' })),
...(await self.registration.getNotifications({ tag: 'read_notification' }))
]
) {
n.close();
}
}, 1000);
});
}

View File

@ -0,0 +1,60 @@
declare var self: ServiceWorkerGlobalScope;
import { get } from 'idb-keyval';
import { pushNotificationDataMap } from '@/types';
import { api } from '@/scripts/operations';
type Accounts = {
[x: string]: {
queue: string[],
timeout: number | null
}
};
class SwNotificationReadManager {
private accounts: Accounts = {};
public async construct() {
const accounts = await get('accounts');
if (!accounts) Error('Accounts are not recorded');
this.accounts = accounts.reduce((acc, e) => {
acc[e.id] = {
queue: [],
timeout: null
};
return acc;
}, {} as Accounts);
return this;
}
// プッシュ通知の既読をサーバーに送信
public async read<K extends keyof pushNotificationDataMap>(data: pushNotificationDataMap[K]) {
if (data.type !== 'notification' || !(data.userId in this.accounts)) return;
const account = this.accounts[data.userId];
account.queue.push(data.body.id as string);
if (account.queue.length >= 20) {
if (account.timeout) clearTimeout(account.timeout);
const notificationIds = account.queue;
account.queue = [];
await api('notifications/read', data.userId, { notificationIds });
return;
}
// 最後の呼び出しから200ms待ってまとめて処理する
if (account.timeout) clearTimeout(account.timeout);
account.timeout = setTimeout(() => {
account.timeout = null;
const notificationIds = account.queue;
account.queue = [];
api('notifications/read', data.userId, { notificationIds });
}, 200);
}
}
export const swNotificationRead = (new SwNotificationReadManager()).construct();

View File

@ -1,8 +1,11 @@
declare var self: ServiceWorkerGlobalScope;
import { createNotification } from '@/scripts/create-notification';
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());
@ -36,33 +39,139 @@ self.addEventListener('push', ev => {
const data: pushNotificationDataMap[K] = ev.data?.json();
switch (data.type) {
// case 'driveFileCreated':
case 'notification':
case 'unreadMessagingMessage':
// クライアントがあったらストリームに接続しているということなので通知しない
if (clients.length != 0) return;
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.notification.close();
ev.waitUntil((async () => {
if (_DEV_) {
console.log('notificationclick', ev.action, ev.notification.data);
}
ev.waitUntil(self.clients.matchAll({
type: "window"
}).then(clientList => {
for (let i = 0; i < clientList.length; i++) {
const client = clientList[i];
if (client.url == '/' && 'focus' in client) {
return client.focus();
}
}
if (self.clients.openWindow) {
return self.clients.openWindow('/');
}
return null;
}));
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']) => {