From 39cf80e19f10676b263004e0a1402fdc5a9613f9 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 9 Apr 2023 17:09:27 +0900 Subject: fix(backend): イベント用redis分離が上手く動かない問題を修正 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/core/AntennaService.ts | 8 ++++---- packages/backend/src/core/CacheService.ts | 8 ++++---- packages/backend/src/core/GlobalEventService.ts | 6 +++--- packages/backend/src/core/MetaService.ts | 8 ++++---- packages/backend/src/core/RoleService.ts | 8 ++++---- packages/backend/src/core/WebhookService.ts | 8 ++++---- 6 files changed, 23 insertions(+), 23 deletions(-) (limited to 'packages/backend/src/core') diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index 35266ac16d..47ebd4c748 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -27,8 +27,8 @@ export class AntennaService implements OnApplicationShutdown { @Inject(DI.redis) private redisClient: Redis.Redis, - @Inject(DI.redisForPubsub) - private redisForPubsub: Redis.Redis, + @Inject(DI.redisForSub) + private redisForSub: Redis.Redis, @Inject(DI.mutingsRepository) private mutingsRepository: MutingsRepository, @@ -52,12 +52,12 @@ export class AntennaService implements OnApplicationShutdown { this.antennasFetched = false; this.antennas = []; - this.redisForPubsub.on('message', this.onRedisMessage); + this.redisForSub.on('message', this.onRedisMessage); } @bindThis public onApplicationShutdown(signal?: string | undefined) { - this.redisForPubsub.off('message', this.onRedisMessage); + this.redisForSub.off('message', this.onRedisMessage); } @bindThis diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts index d74f3e8788..561face5c3 100644 --- a/packages/backend/src/core/CacheService.ts +++ b/packages/backend/src/core/CacheService.ts @@ -27,8 +27,8 @@ export class CacheService implements OnApplicationShutdown { @Inject(DI.redis) private redisClient: Redis.Redis, - @Inject(DI.redisForPubsub) - private redisForPubsub: Redis.Redis, + @Inject(DI.redisForSub) + private redisForSub: Redis.Redis, @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -116,7 +116,7 @@ export class CacheService implements OnApplicationShutdown { fromRedisConverter: (value) => new Set(JSON.parse(value)), }); - this.redisForPubsub.on('message', this.onMessage); + this.redisForSub.on('message', this.onMessage); } @bindThis @@ -167,6 +167,6 @@ export class CacheService implements OnApplicationShutdown { @bindThis public onApplicationShutdown(signal?: string | undefined) { - this.redisForPubsub.off('message', this.onMessage); + this.redisForSub.off('message', this.onMessage); } } diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index 25c064a2b4..9f4de5f985 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -26,8 +26,8 @@ export class GlobalEventService { @Inject(DI.config) private config: Config, - @Inject(DI.redis) - private redisClient: Redis.Redis, + @Inject(DI.redisForPub) + private redisForPub: Redis.Redis, ) { } @@ -37,7 +37,7 @@ export class GlobalEventService { { type: type, body: null } : { type: type, body: value }; - this.redisClient.publish(this.config.host, JSON.stringify({ + this.redisForPub.publish(this.config.host, JSON.stringify({ channel: channel, message: message, })); diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts index 2b6160c82e..1322927c2c 100644 --- a/packages/backend/src/core/MetaService.ts +++ b/packages/backend/src/core/MetaService.ts @@ -14,8 +14,8 @@ export class MetaService implements OnApplicationShutdown { private intervalId: NodeJS.Timer; constructor( - @Inject(DI.redisForPubsub) - private redisForPubsub: Redis.Redis, + @Inject(DI.redisForSub) + private redisForSub: Redis.Redis, @Inject(DI.db) private db: DataSource, @@ -33,7 +33,7 @@ export class MetaService implements OnApplicationShutdown { }, 1000 * 60 * 5); } - this.redisForPubsub.on('message', this.onMessage); + this.redisForSub.on('message', this.onMessage); } @bindThis @@ -122,6 +122,6 @@ export class MetaService implements OnApplicationShutdown { @bindThis public onApplicationShutdown(signal?: string | undefined) { clearInterval(this.intervalId); - this.redisForPubsub.off('message', this.onMessage); + this.redisForSub.off('message', this.onMessage); } } diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index c8ebe1adb7..77645e3f06 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -64,8 +64,8 @@ export class RoleService implements OnApplicationShutdown { public static NotAssignedError = class extends Error {}; constructor( - @Inject(DI.redisForPubsub) - private redisForPubsub: Redis.Redis, + @Inject(DI.redisForSub) + private redisForSub: Redis.Redis, @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -87,7 +87,7 @@ export class RoleService implements OnApplicationShutdown { this.rolesCache = new MemorySingleCache(1000 * 60 * 60 * 1); this.roleAssignmentByUserIdCache = new MemoryKVCache(1000 * 60 * 60 * 1); - this.redisForPubsub.on('message', this.onMessage); + this.redisForSub.on('message', this.onMessage); } @bindThis @@ -400,6 +400,6 @@ export class RoleService implements OnApplicationShutdown { @bindThis public onApplicationShutdown(signal?: string | undefined) { - this.redisForPubsub.off('message', this.onMessage); + this.redisForSub.off('message', this.onMessage); } } diff --git a/packages/backend/src/core/WebhookService.ts b/packages/backend/src/core/WebhookService.ts index 85594f8557..926115613b 100644 --- a/packages/backend/src/core/WebhookService.ts +++ b/packages/backend/src/core/WebhookService.ts @@ -13,14 +13,14 @@ export class WebhookService implements OnApplicationShutdown { private webhooks: Webhook[] = []; constructor( - @Inject(DI.redisForPubsub) - private redisForPubsub: Redis.Redis, + @Inject(DI.redisForSub) + private redisForSub: Redis.Redis, @Inject(DI.webhooksRepository) private webhooksRepository: WebhooksRepository, ) { //this.onMessage = this.onMessage.bind(this); - this.redisForPubsub.on('message', this.onMessage); + this.redisForSub.on('message', this.onMessage); } @bindThis @@ -82,6 +82,6 @@ export class WebhookService implements OnApplicationShutdown { @bindThis public onApplicationShutdown(signal?: string | undefined) { - this.redisForPubsub.off('message', this.onMessage); + this.redisForSub.off('message', this.onMessage); } } -- cgit v1.2.3-freya From 70a06e30d52cfacdfdc4122f9c1a9e09c4bc7e1d Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 10 Apr 2023 10:03:53 +0900 Subject: fix(backend): アンテナのノート、チャンネルのノート、通知が正常に作成できないことがある問題を修正 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix #10482 --- CHANGELOG.md | 11 +++++++++++ packages/backend/src/core/AntennaService.ts | 2 +- packages/backend/src/core/NoteCreateService.ts | 2 +- packages/backend/src/core/NotificationService.ts | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) (limited to 'packages/backend/src/core') diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e6fa2cf4f..114bdc4d75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,17 @@ --> +## 13.x.x (unreleased) + +### General +- + +### Client +- + +### Server +- アンテナのノート、チャンネルのノート、通知が正常に作成できないことがある問題を修正 + ## 13.11.1 ### General diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index 47ebd4c748..02e0b455fd 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -95,7 +95,7 @@ export class AntennaService implements OnApplicationShutdown { this.redisClient.xadd( `antennaTimeline:${antenna.id}`, 'MAXLEN', '~', '200', - `${this.idService.parse(note.id).date.getTime()}-*`, + '*', 'note', note.id); this.globalEventService.publishAntennaStream(antenna.id, 'note', note); diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 5c4d13f178..fb7ee7080a 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -329,7 +329,7 @@ export class NoteCreateService implements OnApplicationShutdown { this.redisClient.xadd( `channelTimeline:${data.channel.id}`, 'MAXLEN', '~', '1000', - `${this.idService.parse(note.id).date.getTime()}-*`, + '*', 'note', note.id); } diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index c44dddea41..c7dac061b2 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -99,7 +99,7 @@ export class NotificationService implements OnApplicationShutdown { const redisIdPromise = this.redisClient.xadd( `notificationTimeline:${notifieeId}`, 'MAXLEN', '~', '300', - `${this.idService.parse(notification.id).date.getTime()}-*`, + '*', 'data', JSON.stringify(notification)); const packed = await this.notificationEntityService.pack(notification, notifieeId, {}); -- cgit v1.2.3-freya From 0702f9775a58900a7a827c23aaaa6aec85b80c50 Mon Sep 17 00:00:00 2001 From: Chimorium <130294972+Chimorium@users.noreply.github.com> Date: Tue, 11 Apr 2023 07:39:46 +0900 Subject: カスタム絵文字のキャッシュ時に"{}"が入ってしまう問題を修正 (#10573) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/core/CustomEmojiService.ts | 8 ++------ packages/backend/src/misc/cache.ts | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) (limited to 'packages/backend/src/core') diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index dc365986fe..3de936dd65 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -43,12 +43,8 @@ export class CustomEmojiService { lifetime: 1000 * 60 * 30, // 30m memoryCacheLifetime: 1000 * 60 * 3, // 3m fetcher: () => this.emojisRepository.find({ where: { host: IsNull() } }).then(emojis => new Map(emojis.map(emoji => [emoji.name, emoji]))), - toRedisConverter: (value) => JSON.stringify(value.values()), - fromRedisConverter: (value) => { - // 原因不明だが配列以外が入ってくることがあるため - if (!Array.isArray(JSON.parse(value))) return undefined; - return new Map(JSON.parse(value).map((x: Emoji) => [x.name, x])); - }, // TODO: Date型の変換 + toRedisConverter: (value) => JSON.stringify(Array.from(value.values())), + fromRedisConverter: (value) => new Map(JSON.parse(value).map((x: Emoji) => [x.name, x])), // TODO: Date型の変換 }); } diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts index a4abd4f878..d35414acf7 100644 --- a/packages/backend/src/misc/cache.ts +++ b/packages/backend/src/misc/cache.ts @@ -8,7 +8,7 @@ export class RedisKVCache { private memoryCache: MemoryKVCache; private fetcher: (key: string) => Promise; private toRedisConverter: (value: T) => string; - private fromRedisConverter: (value: string) => T | undefined; // undefined means no cache + private fromRedisConverter: (value: string) => T; constructor(redisClient: RedisKVCache['redisClient'], name: RedisKVCache['name'], opts: { lifetime: RedisKVCache['lifetime']; @@ -92,7 +92,7 @@ export class RedisSingleCache { private memoryCache: MemorySingleCache; private fetcher: () => Promise; private toRedisConverter: (value: T) => string; - private fromRedisConverter: (value: string) => T | undefined; // undefined means no cache + private fromRedisConverter: (value: string) => T; constructor(redisClient: RedisSingleCache['redisClient'], name: RedisSingleCache['name'], opts: { lifetime: RedisSingleCache['lifetime']; -- cgit v1.2.3-freya From 3a90bcc03cc166632fb64aa76130b63a0ad37a64 Mon Sep 17 00:00:00 2001 From: tamaina Date: Tue, 11 Apr 2023 14:11:39 +0900 Subject: sw: なんかもうめっちゃ変えた (#10570) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * sw: なんかいろいろ * remove debug code * never renotify * update changelog.md --- CHANGELOG.md | 7 +++++ locales/ja-JP.yml | 5 +-- packages/backend/assets/tabler-badges/medal.png | Bin 0 -> 1241 bytes packages/backend/src/core/NotificationService.ts | 1 + .../backend/src/core/PushNotificationService.ts | 5 +++ packages/frontend/src/ui/_common_/sw-inject.ts | 21 ++++++++++--- packages/sw/src/scripts/create-notification.ts | 34 +++++++++++++------- packages/sw/src/scripts/operations.ts | 27 +++++++++++----- packages/sw/src/sw.ts | 35 +++++++++++++++++---- packages/sw/src/types.ts | 2 ++ 10 files changed, 106 insertions(+), 31 deletions(-) create mode 100644 packages/backend/assets/tabler-badges/medal.png (limited to 'packages/backend/src/core') diff --git a/CHANGELOG.md b/CHANGELOG.md index 03d6ccf428..bfd1eca27a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,13 @@ - アンテナのノート、チャンネルのノート、通知が正常に作成できないことがある問題を修正 - ストリーミングのLTLチャンネルでサーバー側にエラーログが出るのを修正 +## Service Worker +- 「通知が既読になったらプッシュ通知を削除する」を復活 + * 「プッシュ通知が更新されました」の挙動を変えた(ホストとバージョンを表示するようにし、一定時間後の削除は行わないように) +- プッシュ通知が実績を解除 (achievementEarned) に対応 +- プッシュ通知のアクションから既存のクライアントの投稿フォームを開くことになった際の挙動を修正 +- たくさんのプッシュ通知を閉じた際、その通知の数だけnotifications/mark-all-as-readを叩くのをやめるように + ## 13.11.1 ### General diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index bdf2cada86..4c5bb60e0c 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -20,6 +20,7 @@ noNotes: "ノートはありません" noNotifications: "通知はありません" instance: "サーバー" settings: "設定" +notificationSettings: "通知の設定" basicSettings: "基本設定" otherSettings: "その他の設定" openInWindow: "ウィンドウで開く" @@ -917,8 +918,8 @@ subscribePushNotification: "プッシュ通知を有効化" unsubscribePushNotification: "プッシュ通知を停止する" pushNotificationAlreadySubscribed: "プッシュ通知は有効です" pushNotificationNotSupported: "ブラウザかサーバーがプッシュ通知に非対応" -sendPushNotificationReadMessage: "通知やメッセージが既読になったらプッシュ通知を削除する" -sendPushNotificationReadMessageCaption: "「{emptyPushNotificationMessage}」という通知が一瞬表示されるようになります。端末の電池消費量が増加する可能性があります。" +sendPushNotificationReadMessage: "通知が既読になったらプッシュ通知を削除する" +sendPushNotificationReadMessageCaption: "端末の電池消費量が増加する可能性があります。" windowMaximize: "最大化" windowMinimize: "最小化" windowRestore: "元に戻す" diff --git a/packages/backend/assets/tabler-badges/medal.png b/packages/backend/assets/tabler-badges/medal.png new file mode 100644 index 0000000000..44dc7b8c1e Binary files /dev/null and b/packages/backend/assets/tabler-badges/medal.png differ diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index c7dac061b2..6691c42836 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -66,6 +66,7 @@ export class NotificationService implements OnApplicationShutdown { @bindThis private postReadAllNotifications(userId: User['id']) { this.globalEventService.publishMainStream(userId, 'readAllNotifications'); + this.pushNotificationService.pushNotification(userId, 'readAllNotifications', undefined); } @bindThis diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts index 69020f7e84..fd24831fbc 100644 --- a/packages/backend/src/core/PushNotificationService.ts +++ b/packages/backend/src/core/PushNotificationService.ts @@ -15,6 +15,7 @@ type PushNotificationsTypes = { antenna: { id: string, name: string }; note: Packed<'Note'>; }; + 'readAllNotifications': undefined; }; // Reduce length because push message servers have character limits @@ -68,6 +69,10 @@ export class PushNotificationService { }); for (const subscription of subscriptions) { + if ([ + 'readAllNotifications', + ].includes(type) && !subscription.sendReadMessage) continue; + const pushSubscription = { endpoint: subscription.endpoint, keys: { diff --git a/packages/frontend/src/ui/_common_/sw-inject.ts b/packages/frontend/src/ui/_common_/sw-inject.ts index a92a06bd3e..c97e2b567b 100644 --- a/packages/frontend/src/ui/_common_/sw-inject.ts +++ b/packages/frontend/src/ui/_common_/sw-inject.ts @@ -1,17 +1,18 @@ -import { post } from '@/os'; +import { api, post } from '@/os'; import { $i, login } from '@/account'; import { getAccountFromId } from '@/scripts/get-account-from-id'; import { mainRouter } from '@/router'; +import { deepClone } from '@/scripts/clone'; export function swInject() { - navigator.serviceWorker.addEventListener('message', ev => { + navigator.serviceWorker.addEventListener('message', async ev => { if (_DEV_) { console.log('sw msg', ev.data); } if (ev.data.type !== 'order') return; - if (ev.data.loginId !== $i?.id) { + if (ev.data.loginId && ev.data.loginId !== $i?.id) { return getAccountFromId(ev.data.loginId).then(account => { if (!account) return; return login(account.token, ev.data.url); @@ -19,8 +20,18 @@ export function swInject() { } switch (ev.data.order) { - case 'post': - return post(ev.data.options); + case 'post': { + const props = deepClone(ev.data.options); + // プッシュ通知から来たreply,renoteはtruncateBodyが通されているため、 + // 完全なノートを取得しなおす + if (props.reply) { + props.reply = await api('notes/show', { noteId: props.reply.id }); + } + if (props.renote) { + props.renote = await api('notes/show', { noteId: props.renote.id }); + } + return post(props); + } case 'push': if (mainRouter.currentRoute.value.path === ev.data.url) { return window.scroll({ top: 0, behavior: 'smooth' }); diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts index 8e214c9850..4d27858253 100644 --- a/packages/sw/src/scripts/create-notification.ts +++ b/packages/sw/src/scripts/create-notification.ts @@ -21,7 +21,7 @@ const iconUrl = (name: BadgeNames) => `/static-assets/tabler-badges/${name}.png` * 1. Find the icon and download png from https://tabler-icons.io/ * 2. vips resize ~/Downloads/icon-name.png vipswork.png 0.4; vips scRGB2BW vipswork.png ~/icon-name.png"[compression=9,strip]"; rm vipswork.png; * 3. mv ~/icon-name.png ~/misskey/packages/backend/assets/tabler-badges/ - * 4. Add 'icon-name' to badgeNames + * 4. Add 'icon-name' to BadgeNames * 5. Add `badge: iconUrl('icon-name'),` */ @@ -168,14 +168,6 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif }]; } - case 'pollEnded': - return [t('_notification.pollEnded'), { - body: data.body.note.text || '', - badge: iconUrl('chart-arrows'), - tag: `poll:${data.body.note.id}`, - data, - }]; - case 'receiveFollowRequest': return [t('_notification.youReceivedFollowRequest'), { body: getUserName(data.body.user), @@ -202,6 +194,14 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif data, }]; + case 'achievementEarned': + return [t('_notification.achievementEarned'), { + body: t(`_achievements._types._${data.body.achievement}.title`), + badge: iconUrl('medal'), + data, + tag: `achievement:${data.body.achievement}`, + }]; + case 'app': return [data.body.header ?? data.body.body, { body: data.body.header ? data.body.body : '', @@ -233,17 +233,29 @@ export async function createEmptyNotification() { const { t } = i18n; await globalThis.registration.showNotification( - t('_notification.emptyPushNotificationMessage'), + (new URL(origin)).host, { + body: `Misskey v${_VERSION_}`, silent: true, badge: iconUrl('null'), tag: 'read_notification', + actions: [ + { + action: 'markAllAsRead', + title: t('markAllAsRead'), + }, + { + action: 'settings', + title: t('notificationSettings'), + }, + ], + data: {}, }, ); setTimeout(async () => { try { - await closeNotificationsByTags(['user_visible_auto_notification', 'read_notification']); + await closeNotificationsByTags(['user_visible_auto_notification']); } finally { res(); } diff --git a/packages/sw/src/scripts/operations.ts b/packages/sw/src/scripts/operations.ts index 0978cf9a31..2fd02f9dcb 100644 --- a/packages/sw/src/scripts/operations.ts +++ b/packages/sw/src/scripts/operations.ts @@ -4,7 +4,6 @@ */ import * as Misskey from 'misskey-js'; import { SwMessage, SwMessageOrderType } from '@/types'; -import { acct as getAcct } from '@/filters/user'; import { getAccountFromId } from '@/scripts/get-account-from-id'; import { getUrlWithLoginId } from '@/scripts/login-id'; @@ -17,13 +16,27 @@ export async function api(endpoint: E, userId return cli.request(endpoint, options, account.token); } +// mark-all-as-read送出を1秒間隔に制限する +const readBlockingStatus = new Map(); +export function sendMarkAllAsRead(userId: string): Promise { + if (readBlockingStatus.get(userId)) return Promise.resolve(); + readBlockingStatus.set(userId, true); + return new Promise(resolve => { + setTimeout(() => { + readBlockingStatus.set(userId, false); + api('notifications/mark-all-as-read', userId) + .then(resolve, resolve); + }, 1000); + }); +} + // rendered acctからユーザーを開く -export function openUser(acct: string, loginId: string) { +export function openUser(acct: string, loginId?: string) { return openClient('push', `/@${acct}`, loginId, { acct }); } // noteIdからノートを開く -export function openNote(noteId: string, loginId: string) { +export function openNote(noteId: string, loginId?: string) { return openClient('push', `/notes/${noteId}`, loginId, { noteId }); } @@ -33,7 +46,7 @@ export function openAntenna(antennaId: string, loginId: string) { } // post-formのオプションから投稿フォームを開く -export async function openPost(options: any, loginId: string) { +export async function openPost(options: any, loginId?: string) { // クエリを作成しておく let url = '/share?'; if (options.initialText) url += `text=${options.initialText}&`; @@ -43,7 +56,7 @@ export async function openPost(options: any, loginId: string) { return openClient('post', url, loginId, { options }); } -export async function openClient(order: SwMessageOrderType, url: string, loginId: string, query: any = {}) { +export async function openClient(order: SwMessageOrderType, url: string, loginId?: string, query: any = {}) { const client = await findClient(); if (client) { @@ -51,7 +64,7 @@ export async function openClient(order: SwMessageOrderType, url: string, loginId return client; } - return globalThis.clients.openWindow(getUrlWithLoginId(url, loginId)); + return globalThis.clients.openWindow(loginId ? getUrlWithLoginId(url, loginId) : url); } export async function findClient() { @@ -59,7 +72,7 @@ export async function findClient() { type: 'window', }); for (const c of clients) { - if (!new URL(c.url).searchParams.has('zen')) return c; + if (!(new URL(c.url)).searchParams.has('zen')) return c; } return null; } diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts index 56050987a0..c1cde8b3c2 100644 --- a/packages/sw/src/sw.ts +++ b/packages/sw/src/sw.ts @@ -1,9 +1,9 @@ import { createEmptyNotification, createNotification } from '@/scripts/create-notification'; import { swLang } from '@/scripts/lang'; -import { api } from '@/scripts/operations'; import { PushNotificationDataMap } from '@/types'; import * as swos from '@/scripts/operations'; import { acct as getAcct } from '@/filters/user'; +import { get } from 'idb-keyval'; globalThis.addEventListener('install', ev => { //ev.waitUntil(globalThis.skipWaiting()); @@ -54,6 +54,10 @@ globalThis.addEventListener('push', ev => { if ((new Date()).getTime() - data.dateTime > 1000 * 60 * 60 * 24) break; return createNotification(data); + case 'readAllNotifications': + await globalThis.registration.getNotifications() + .then(notifications => notifications.forEach(n => n.close())); + break; } await createEmptyNotification(); @@ -68,7 +72,7 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv } const { action, notification } = ev; - const data: PushNotificationDataMap[keyof PushNotificationDataMap] = notification.data; + const data: PushNotificationDataMap[keyof PushNotificationDataMap] = notification.data ?? {}; const { userId: loginId } = data; let client: WindowClient | null = null; @@ -124,13 +128,29 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv break; case 'unreadAntennaNote': client = await swos.openAntenna(data.body.antenna.id, loginId); + break; + default: + switch (action) { + case 'markAllAsRead': + await globalThis.registration.getNotifications() + .then(notifications => notifications.forEach(n => n.close())); + await get('accounts').then(accounts => { + return Promise.all(accounts.map(async account => { + await swos.sendMarkAllAsRead(account.id); + })); + }); + break; + case 'settings': + client = await swos.openClient('push', '/settings/notifications', loginId); + break; + } } if (client) { client.focus(); } if (data.type === 'notification') { - api('notifications/mark-all-as-read', data.userId); + await swos.sendMarkAllAsRead(loginId); } notification.close(); @@ -140,9 +160,12 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv globalThis.addEventListener('notificationclose', (ev: ServiceWorkerGlobalScopeEventMap['notificationclose']) => { const data: PushNotificationDataMap[keyof PushNotificationDataMap] = ev.notification.data; - if (data.type === 'notification') { - api('notifications/mark-all-as-read', data.userId); - } + ev.waitUntil((async () => { + if (data.type === 'notification') { + await swos.sendMarkAllAsRead(data.userId); + } + return; + })()); }); globalThis.addEventListener('message', (ev: ServiceWorkerGlobalScopeEventMap['message']) => { diff --git a/packages/sw/src/types.ts b/packages/sw/src/types.ts index 7b653e94b7..204ec6198d 100644 --- a/packages/sw/src/types.ts +++ b/packages/sw/src/types.ts @@ -17,6 +17,7 @@ type PushNotificationDataSourceMap = { antenna: { id: string, name: string }; note: Misskey.entities.Note; }; + readAllNotifications: undefined; }; export type PushNotificationData = { @@ -37,6 +38,7 @@ export type BadgeNames = | 'at' | 'chart-arrows' | 'circle-check' + | 'medal' | 'messages' | 'plus' | 'quote' -- cgit v1.2.3-freya From c10d591bd09f18f939f21650def76a422d01d78b Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 11 Apr 2023 14:20:16 +0900 Subject: perf(backend): cache swSubscriptions --- .../backend/src/core/PushNotificationService.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) (limited to 'packages/backend/src/core') diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts index fd24831fbc..9b44cf6413 100644 --- a/packages/backend/src/core/PushNotificationService.ts +++ b/packages/backend/src/core/PushNotificationService.ts @@ -1,12 +1,14 @@ import { Inject, Injectable } from '@nestjs/common'; import push from 'web-push'; +import Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import type { Packed } from '@/misc/json-schema'; import { getNoteSummary } from '@/misc/get-note-summary.js'; -import type { SwSubscriptionsRepository } from '@/models/index.js'; +import type { SwSubscription, SwSubscriptionsRepository } from '@/models/index.js'; import { MetaService } from '@/core/MetaService.js'; import { bindThis } from '@/decorators.js'; +import { RedisKVCache } from '@/misc/cache.js'; // Defined also packages/sw/types.ts#L13 type PushNotificationsTypes = { @@ -41,15 +43,27 @@ function truncateBody(type: T, body: Pus @Injectable() export class PushNotificationService { + private subscriptionsCache: RedisKVCache; + constructor( @Inject(DI.config) private config: Config, + @Inject(DI.redis) + private redisClient: Redis.Redis, + @Inject(DI.swSubscriptionsRepository) private swSubscriptionsRepository: SwSubscriptionsRepository, private metaService: MetaService, ) { + this.subscriptionsCache = new RedisKVCache(this.redisClient, 'userSwSubscriptions', { + lifetime: 1000 * 60 * 60 * 1, // 1h + memoryCacheLifetime: 1000 * 60 * 3, // 3m + fetcher: (key) => this.swSubscriptionsRepository.findBy({ userId: key }), + toRedisConverter: (value) => JSON.stringify(value), + fromRedisConverter: (value) => JSON.parse(value), + }); } @bindThis @@ -63,10 +77,7 @@ export class PushNotificationService { meta.swPublicKey, meta.swPrivateKey); - // Fetch - const subscriptions = await this.swSubscriptionsRepository.findBy({ - userId: userId, - }); + const subscriptions = await this.subscriptionsCache.fetch(userId); for (const subscription of subscriptions) { if ([ -- cgit v1.2.3-freya