Revert "Fix notification (#2654)"
This reverts commit 09cb1bba713072e075858a24eee2712076793dde.
This commit is contained in:
parent
d03e9a4da1
commit
9ebed95fb6
@ -38,8 +38,10 @@ export async function readNotificationByQuery(
|
|||||||
|
|
||||||
function postReadAllNotifications(userId: User['id']) {
|
function postReadAllNotifications(userId: User['id']) {
|
||||||
publishMainStream(userId, 'readAllNotifications');
|
publishMainStream(userId, 'readAllNotifications');
|
||||||
|
return pushNotification(userId, 'readAllNotifications', undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
function postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) {
|
function postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) {
|
||||||
publishMainStream(userId, 'readNotifications', notificationIds);
|
publishMainStream(userId, 'readNotifications', notificationIds);
|
||||||
|
return pushNotification(userId, 'readNotifications', { notificationIds });
|
||||||
}
|
}
|
||||||
|
@ -29,4 +29,5 @@ export default define(meta, paramDef, async (ps, user) => {
|
|||||||
|
|
||||||
// 全ての通知を読みましたよというイベントを発行
|
// 全ての通知を読みましたよというイベントを発行
|
||||||
publishMainStream(user.id, 'readAllNotifications');
|
publishMainStream(user.id, 'readAllNotifications');
|
||||||
|
pushNotification(user.id, 'readAllNotifications', undefined);
|
||||||
});
|
});
|
||||||
|
@ -20,14 +20,30 @@ export const meta = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
|
oneOf: [
|
||||||
|
{
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
notificationId: { type: 'string', format: 'misskey:id' },
|
notificationId: { type: 'string', format: 'misskey:id' },
|
||||||
},
|
},
|
||||||
required: ['notificationId'],
|
required: ['notificationId'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
notificationIds: {
|
||||||
|
type: 'array',
|
||||||
|
items: { type: 'string', format: 'misskey:id' },
|
||||||
|
maxItems: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['notificationIds'],
|
||||||
|
},
|
||||||
|
],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-default-export
|
// eslint-disable-next-line import/no-default-export
|
||||||
export default define(meta, paramDef, async (ps, user) => {
|
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);
|
||||||
});
|
});
|
||||||
|
@ -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({
|
push.sendNotification(pushSubscription, JSON.stringify({
|
||||||
type,
|
type,
|
||||||
body: type === 'notification' ? truncateNotification(body as Packed<'Notification'>) : body,
|
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,
|
proxy: config.proxy,
|
||||||
}).catch((err: any) => {
|
}).catch((err: any) => {
|
||||||
//console.log(err.statusCode);
|
//swLogger.info(err.statusCode);
|
||||||
//console.log(err.headers);
|
//swLogger.info(err.headers);
|
||||||
//console.log(err.body);
|
//swLogger.info(err.body);
|
||||||
|
|
||||||
if (err.statusCode === 410) {
|
if (err.statusCode === 410) {
|
||||||
SwSubscriptions.delete({
|
SwSubscriptions.delete({
|
||||||
|
@ -21,6 +21,7 @@ export async function createNotification<K extends keyof pushNotificationDataMap
|
|||||||
return self.registration.showNotification(...n);
|
return self.registration.showNotification(...n);
|
||||||
} else {
|
} else {
|
||||||
console.error('Could not compose notification', data);
|
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を直接使用
|
// users/showの型定義をswos.apiへ当てはめるのが困難なのでapiFetch.requestを直接使用
|
||||||
const account = await getAccountFromId(data.userId);
|
const account = await getAccountFromId(data.userId);
|
||||||
if (!account) return null;
|
if (!account) return null;
|
||||||
|
const userDetail = await cli.request('users/show', { userId: data.body.userId }, account.token);
|
||||||
return [t('_notification.youWereFollowed'), {
|
return [t('_notification.youWereFollowed'), {
|
||||||
body: getUserName(data.body.user),
|
body: getUserName(data.body.user),
|
||||||
icon: data.body.user.avatarUrl,
|
icon: data.body.user.avatarUrl,
|
||||||
badge: iconUrl('user-plus'),
|
badge: iconUrl('user-plus'),
|
||||||
data,
|
data,
|
||||||
|
actions: userDetail.isFollowing ? [] : [
|
||||||
|
{
|
||||||
|
action: 'follow',
|
||||||
|
title: t('_notification._actions.followBack')
|
||||||
|
}
|
||||||
|
],
|
||||||
}];
|
}];
|
||||||
|
|
||||||
case 'mention':
|
case 'mention':
|
||||||
@ -56,6 +64,12 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
|
|||||||
icon: data.body.user.avatarUrl,
|
icon: data.body.user.avatarUrl,
|
||||||
badge: iconUrl('at'),
|
badge: iconUrl('at'),
|
||||||
data,
|
data,
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
action: 'reply',
|
||||||
|
title: t('_notification._actions.reply')
|
||||||
|
}
|
||||||
|
],
|
||||||
}];
|
}];
|
||||||
|
|
||||||
case 'reply':
|
case 'reply':
|
||||||
@ -64,6 +78,12 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
|
|||||||
icon: data.body.user.avatarUrl,
|
icon: data.body.user.avatarUrl,
|
||||||
badge: iconUrl('reply'),
|
badge: iconUrl('reply'),
|
||||||
data,
|
data,
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
action: 'reply',
|
||||||
|
title: t('_notification._actions.reply')
|
||||||
|
}
|
||||||
|
],
|
||||||
}];
|
}];
|
||||||
|
|
||||||
case 'renote':
|
case 'renote':
|
||||||
@ -72,6 +92,12 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
|
|||||||
icon: data.body.user.avatarUrl,
|
icon: data.body.user.avatarUrl,
|
||||||
badge: iconUrl('retweet'),
|
badge: iconUrl('retweet'),
|
||||||
data,
|
data,
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
action: 'showUser',
|
||||||
|
title: getUserName(data.body.user)
|
||||||
|
}
|
||||||
|
],
|
||||||
}];
|
}];
|
||||||
|
|
||||||
case 'quote':
|
case 'quote':
|
||||||
@ -80,6 +106,18 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
|
|||||||
icon: data.body.user.avatarUrl,
|
icon: data.body.user.avatarUrl,
|
||||||
badge: iconUrl('quote-right'),
|
badge: iconUrl('quote-right'),
|
||||||
data,
|
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':
|
case 'reaction':
|
||||||
@ -122,6 +160,12 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
|
|||||||
icon: data.body.user.avatarUrl,
|
icon: data.body.user.avatarUrl,
|
||||||
badge,
|
badge,
|
||||||
data,
|
data,
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
action: 'showUser',
|
||||||
|
title: getUserName(data.body.user)
|
||||||
|
}
|
||||||
|
],
|
||||||
}];
|
}];
|
||||||
|
|
||||||
case 'pollVote':
|
case 'pollVote':
|
||||||
@ -145,6 +189,16 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
|
|||||||
icon: data.body.user.avatarUrl,
|
icon: data.body.user.avatarUrl,
|
||||||
badge: iconUrl('clock'),
|
badge: iconUrl('clock'),
|
||||||
data,
|
data,
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
action: 'accept',
|
||||||
|
title: t('accept')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: 'reject',
|
||||||
|
title: t('reject')
|
||||||
|
}
|
||||||
|
],
|
||||||
}];
|
}];
|
||||||
|
|
||||||
case 'followRequestAccepted':
|
case 'followRequestAccepted':
|
||||||
@ -160,6 +214,16 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
|
|||||||
body: data.body.invitation.group.name,
|
body: data.body.invitation.group.name,
|
||||||
badge: iconUrl('id-card-alt'),
|
badge: iconUrl('id-card-alt'),
|
||||||
data,
|
data,
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
action: 'accept',
|
||||||
|
title: t('accept')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: 'reject',
|
||||||
|
title: t('reject')
|
||||||
|
}
|
||||||
|
],
|
||||||
}];
|
}];
|
||||||
|
|
||||||
case 'app':
|
case 'app':
|
||||||
@ -193,3 +257,33 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
|
|||||||
return null;
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
60
packages/sw/src/scripts/notification-read.ts
Normal file
60
packages/sw/src/scripts/notification-read.ts
Normal 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();
|
@ -1,8 +1,11 @@
|
|||||||
declare var self: ServiceWorkerGlobalScope;
|
declare var self: ServiceWorkerGlobalScope;
|
||||||
|
|
||||||
import { createNotification } from '@/scripts/create-notification';
|
import { createEmptyNotification, createNotification } from '@/scripts/create-notification';
|
||||||
import { swLang } from '@/scripts/lang';
|
import { swLang } from '@/scripts/lang';
|
||||||
|
import { swNotificationRead } from '@/scripts/notification-read';
|
||||||
import { pushNotificationDataMap } from '@/types';
|
import { pushNotificationDataMap } from '@/types';
|
||||||
|
import * as swos from '@/scripts/operations';
|
||||||
|
import { acct as getAcct } from '@/filters/user';
|
||||||
|
|
||||||
self.addEventListener('install', ev => {
|
self.addEventListener('install', ev => {
|
||||||
ev.waitUntil(self.skipWaiting());
|
ev.waitUntil(self.skipWaiting());
|
||||||
@ -36,33 +39,139 @@ self.addEventListener('push', ev => {
|
|||||||
const data: pushNotificationDataMap[K] = ev.data?.json();
|
const data: pushNotificationDataMap[K] = ev.data?.json();
|
||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
|
// case 'driveFileCreated':
|
||||||
case 'notification':
|
case 'notification':
|
||||||
case 'unreadMessagingMessage':
|
case 'unreadMessagingMessage':
|
||||||
// クライアントがあったらストリームに接続しているということなので通知しない
|
// クライアントがあったらストリームに接続しているということなので通知しない
|
||||||
if (clients.length != 0) return;
|
if (clients.length != 0) return;
|
||||||
return createNotification(data);
|
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']) => {
|
self.addEventListener('notificationclick', <K extends keyof pushNotificationDataMap>(ev: ServiceWorkerGlobalScopeEventMap['notificationclick']) => {
|
||||||
ev.notification.close();
|
ev.waitUntil((async () => {
|
||||||
|
if (_DEV_) {
|
||||||
ev.waitUntil(self.clients.matchAll({
|
console.log('notificationclick', ev.action, ev.notification.data);
|
||||||
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('/');
|
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;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}));
|
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']) => {
|
self.addEventListener('message', (ev: ServiceWorkerGlobalScopeEventMap['message']) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user