From 36170a11f5b889cae3feda6bd4c666aafee98fef Mon Sep 17 00:00:00 2001 From: tamaina Date: Sat, 18 Feb 2023 05:16:34 +0000 Subject: [PATCH 01/24] refactor(sw): self => globalThis --- packages/sw/src/scripts/create-notification.ts | 8 ++++---- packages/sw/src/scripts/operations.ts | 4 ++-- packages/sw/src/sw.ts | 14 +++++++------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts index c121b30bef62..e45c3f504c9d 100644 --- a/packages/sw/src/scripts/create-notification.ts +++ b/packages/sw/src/scripts/create-notification.ts @@ -23,7 +23,7 @@ export async function createNotification; const { t } = i18n; - await self.registration.showNotification( + await globalThis.registration.showNotification( t('_notification.emptyPushNotificationMessage'), { silent: true, @@ -253,8 +253,8 @@ export async function createEmptyNotification() { setTimeout(async () => { for (const n of [ - ...(await self.registration.getNotifications({ tag: 'user_visible_auto_notification' })), - ...(await self.registration.getNotifications({ tag: 'read_notification' })), + ...(await globalThis.registration.getNotifications({ tag: 'user_visible_auto_notification' })), + ...(await globalThis.registration.getNotifications({ tag: 'read_notification' })), ] ) { n.close(); diff --git a/packages/sw/src/scripts/operations.ts b/packages/sw/src/scripts/operations.ts index 4d693223b2fc..8936a7763afe 100644 --- a/packages/sw/src/scripts/operations.ts +++ b/packages/sw/src/scripts/operations.ts @@ -51,11 +51,11 @@ export async function openClient(order: swMessageOrderType, url: string, loginId return client; } - return self.clients.openWindow(getUrlWithLoginId(url, loginId)); + return globalThis.clients.openWindow(getUrlWithLoginId(url, loginId)); } export async function findClient() { - const clients = await self.clients.matchAll({ + const clients = await globalThis.clients.matchAll({ type: 'window', }); for (const c of clients) { diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts index c392d03232da..f4d76854709e 100644 --- a/packages/sw/src/sw.ts +++ b/packages/sw/src/sw.ts @@ -6,7 +6,7 @@ import * as swos from '@/scripts/operations'; import { acct as getAcct } from '@/filters/user'; globalThis.addEventListener('install', ev => { - //ev.waitUntil(self.skipWaiting()); + //ev.waitUntil(globalThis.skipWaiting()); }); globalThis.addEventListener('activate', ev => { @@ -17,7 +17,7 @@ globalThis.addEventListener('activate', ev => { .filter((v) => v !== swLang.cacheName) .map(name => caches.delete(name)), )) - .then(() => self.clients.claim()), + .then(() => globalThis.clients.claim()), ); }); @@ -40,7 +40,7 @@ globalThis.addEventListener('fetch', ev => { globalThis.addEventListener('push', ev => { // クライアント取得 - ev.waitUntil(self.clients.matchAll({ + ev.waitUntil(globalThis.clients.matchAll({ includeUncontrolled: true, type: 'window', }).then(async (clients: readonly WindowClient[]) => { @@ -58,24 +58,24 @@ globalThis.addEventListener('push', ev => { return createNotification(data); case 'readAllNotifications': - for (const n of await self.registration.getNotifications()) { + for (const n of await globalThis.registration.getNotifications()) { if (n?.data?.type === 'notification') n.close(); } break; case 'readAllAntennas': - for (const n of await self.registration.getNotifications()) { + for (const n of await globalThis.registration.getNotifications()) { if (n?.data?.type === 'unreadAntennaNote') n.close(); } break; case 'readNotifications': - for (const n of await self.registration.getNotifications()) { + for (const n of await globalThis.registration.getNotifications()) { if (data.body.notificationIds.includes(n.data.body.id)) { n.close(); } } break; case 'readAntenna': - for (const n of await self.registration.getNotifications()) { + for (const n of await globalThis.registration.getNotifications()) { if (n?.data?.type === 'unreadAntennaNote' && data.body.antennaId === n.data.body.antenna.id) { n.close(); } From 8c883653c959fff5de4ddc24d8ee64f59230f880 Mon Sep 17 00:00:00 2001 From: tamaina Date: Sat, 18 Feb 2023 17:48:20 +0900 Subject: [PATCH 02/24] =?UTF-8?q?fix/enhance(sw):=20=E3=83=97=E3=83=83?= =?UTF-8?q?=E3=82=B7=E3=83=A5=E9=80=9A=E7=9F=A5=20(=E3=83=90=E3=83=83?= =?UTF-8?q?=E3=82=AF=E3=82=B0=E3=83=A9=E3=82=A6=E3=83=B3=E3=83=89=E3=81=A7?= =?UTF-8?q?=E9=96=8B=E3=81=84=E3=81=A6=E3=81=84=E3=82=8B=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=82=82=E9=80=9A=E7=9F=A5,=20=E3=83=AA=E3=82=A2=E3=82=AF?= =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3=E9=80=9A=E7=9F=A5=E3=81=AF=E3=83=8E?= =?UTF-8?q?=E3=83=BC=E3=83=88=E3=81=AB=E3=81=A4=E3=81=8D1=E3=81=A4?= =?UTF-8?q?=E3=81=AB)=20(#9977)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(sw): クライアントがあってもpush notificationを無視しない 「プッシュ通知を更新しました」の原因になるため * enhance(sw): リアクション通知は1つのノートにつき1つしか表示しない Safari対応で、通知tagは能動的に閉じるように * revert closeNotificationsByTags --- .../sw/src/scripts/create-notification.ts | 26 +++++++++++-------- packages/sw/src/sw.ts | 6 ++--- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts index e45c3f504c9d..14f96816a363 100644 --- a/packages/sw/src/scripts/create-notification.ts +++ b/packages/sw/src/scripts/create-notification.ts @@ -10,6 +10,12 @@ import { getAccountFromId } from '@/scripts/get-account-from-id'; import { char2fileName } from '@/scripts/twemoji-base'; import * as url from '@/scripts/url'; +const closeNotificationsByTags = async (tags: string[]) => { + for (const n of (await Promise.all(tags.map(tag => globalThis.registration.getNotifications({ tag })))).flat()) { + n.close(); + } +} + const iconUrl = (name: badgeNames) => `/static-assets/tabler-badges/${name}.png`; /* How to add a new badge: * 1. Find the icon and download png from https://tabler-icons.io/ @@ -161,6 +167,7 @@ async function composeNotification(data: pushNotificationDataMap[keyof pushNotif badge = iconUrl('plus'); } + const tag = `reaction:${data.body.note.id}`; return [`${reaction} ${getUserName(data.body.user)}`, { body: data.body.note.text ?? '', icon: data.body.user.avatarUrl, @@ -176,10 +183,11 @@ async function composeNotification(data: pushNotificationDataMap[keyof pushNotif } case 'pollEnded': + const tag = `poll:${data.body.note.id}`; return [t('_notification.pollEnded'), { body: data.body.note.text || '', badge: iconUrl('chart-arrows'), - tag: `poll:${data.body.note.id}`, + tag, data, }]; @@ -220,11 +228,12 @@ async function composeNotification(data: pushNotificationDataMap[keyof pushNotif return null; } case 'unreadAntennaNote': + const tag = `antenna:${data.body.antenna.id}`; return [t('_notification.unreadAntennaNote', { name: data.body.antenna.name }), { body: `${getUserName(data.body.note.user)}: ${data.body.note.text ?? ''}`, icon: data.body.note.user.avatarUrl, badge: iconUrl('antenna'), - tag: `antenna:${data.body.antenna.id}`, + tag, data, renotify: true, }]; @@ -248,16 +257,11 @@ export async function createEmptyNotification() { }, ); - res(); - setTimeout(async () => { - for (const n of - [ - ...(await globalThis.registration.getNotifications({ tag: 'user_visible_auto_notification' })), - ...(await globalThis.registration.getNotifications({ tag: 'read_notification' })), - ] - ) { - n.close(); + try { + await closeNotificationsByTags(['user_visible_auto_notification', 'read_notification']); + } finally { + res(); } }, 1000); }); diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts index f4d76854709e..6f4c487354f4 100644 --- a/packages/sw/src/sw.ts +++ b/packages/sw/src/sw.ts @@ -53,9 +53,6 @@ globalThis.addEventListener('push', ev => { // 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 globalThis.registration.getNotifications()) { @@ -83,7 +80,8 @@ globalThis.addEventListener('push', ev => { break; } - return createEmptyNotification(); + await createEmptyNotification(); + return; })); }); From cd5615d3549e7b46d670b863e77e66a32de80da9 Mon Sep 17 00:00:00 2001 From: tamaina Date: Sat, 18 Feb 2023 14:11:45 +0000 Subject: [PATCH 03/24] fix lint --- packages/sw/src/scripts/create-notification.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts index 14f96816a363..da92b37d1986 100644 --- a/packages/sw/src/scripts/create-notification.ts +++ b/packages/sw/src/scripts/create-notification.ts @@ -14,7 +14,7 @@ const closeNotificationsByTags = async (tags: string[]) => { for (const n of (await Promise.all(tags.map(tag => globalThis.registration.getNotifications({ tag })))).flat()) { n.close(); } -} +}; const iconUrl = (name: badgeNames) => `/static-assets/tabler-badges/${name}.png`; /* How to add a new badge: @@ -183,11 +183,10 @@ async function composeNotification(data: pushNotificationDataMap[keyof pushNotif } case 'pollEnded': - const tag = `poll:${data.body.note.id}`; return [t('_notification.pollEnded'), { body: data.body.note.text || '', badge: iconUrl('chart-arrows'), - tag, + tag: `poll:${data.body.note.id}`, data, }]; @@ -228,12 +227,11 @@ async function composeNotification(data: pushNotificationDataMap[keyof pushNotif return null; } case 'unreadAntennaNote': - const tag = `antenna:${data.body.antenna.id}`; return [t('_notification.unreadAntennaNote', { name: data.body.antenna.name }), { body: `${getUserName(data.body.note.user)}: ${data.body.note.text ?? ''}`, icon: data.body.note.user.avatarUrl, badge: iconUrl('antenna'), - tag, + tag: `antenna:${data.body.antenna.id}`, data, renotify: true, }]; From 2aa73fdf6c91f6f1ec01c5d50d046f2e6e970faf Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Sun, 19 Feb 2023 07:27:14 +0100 Subject: [PATCH 04/24] test(backend): restore AP unit tests (#9987) --- packages/backend/test/misc/mock-resolver.ts | 34 ++++++++++++- .../test/{tests => unit}/activitypub.ts | 49 +++++++++++++------ 2 files changed, 66 insertions(+), 17 deletions(-) rename packages/backend/test/{tests => unit}/activitypub.ts (56%) diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts index 9efed267eaff..6b31e68616af 100644 --- a/packages/backend/test/misc/mock-resolver.ts +++ b/packages/backend/test/misc/mock-resolver.ts @@ -1,5 +1,16 @@ -import Resolver from '../../src/activitypub/resolver.js'; -import { IObject } from '../../src/activitypub/type.js'; +import type { Config } from '@/config.js'; +import type { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js'; +import type { ApRendererService } from '@/core/activitypub/ApRendererService.js'; +import type { ApRequestService } from '@/core/activitypub/ApRequestService.js'; +import { Resolver } from '@/core/activitypub/ApResolverService.js'; +import type { IObject } from '@/core/activitypub/type.js'; +import type { HttpRequestService } from '@/core/HttpRequestService.js'; +import type { InstanceActorService } from '@/core/InstanceActorService.js'; +import type { LoggerService } from '@/core/LoggerService.js'; +import type { MetaService } from '@/core/MetaService.js'; +import type { UtilityService } from '@/core/UtilityService.js'; +import { bindThis } from '@/decorators.js'; +import type { NoteReactionsRepository, NotesRepository, PollsRepository, UsersRepository } from '@/models/index.js'; type MockResponse = { type: string; @@ -8,6 +19,25 @@ type MockResponse = { export class MockResolver extends Resolver { private _rs = new Map(); + + constructor(loggerService: LoggerService) { + super( + {} as Config, + {} as UsersRepository, + {} as NotesRepository, + {} as PollsRepository, + {} as NoteReactionsRepository, + {} as UtilityService, + {} as InstanceActorService, + {} as MetaService, + {} as ApRequestService, + {} as HttpRequestService, + {} as ApRendererService, + {} as ApDbResolverService, + loggerService, + ); + } + public async _register(uri: string, content: string | Record, type = 'application/activity+json') { this._rs.set(uri, { type, diff --git a/packages/backend/test/tests/activitypub.ts b/packages/backend/test/unit/activitypub.ts similarity index 56% rename from packages/backend/test/tests/activitypub.ts rename to packages/backend/test/unit/activitypub.ts index 19fb5d90d71d..3d0032507e9c 100644 --- a/packages/backend/test/tests/activitypub.ts +++ b/packages/backend/test/unit/activitypub.ts @@ -2,8 +2,39 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import rndstr from 'rndstr'; +import { Test } from '@nestjs/testing'; +import { jest } from '@jest/globals'; + +import { ApNoteService } from '@/core/activitypub/models/ApNoteService.js'; +import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; +import { GlobalModule } from '@/GlobalModule.js'; +import { CoreModule } from '@/core/CoreModule.js'; +import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; +import { LoggerService } from '@/core/LoggerService.js'; +import { MockResolver } from '../misc/mock-resolver.js'; describe('ActivityPub', () => { + let noteService: ApNoteService; + let personService: ApPersonService; + let resolver: MockResolver; + + beforeEach(async () => { + const app = await Test.createTestingModule({ + imports: [GlobalModule, CoreModule], + }).compile(); + + await app.init(); + app.enableShutdownHooks(); + + noteService = app.get(ApNoteService); + personService = app.get(ApPersonService); + resolver = new MockResolver(await app.resolve(LoggerService)); + + // Prevent ApPersonService from fetching instance, as it causes Jest import-after-test error + const federatedInstanceService = app.get(FederatedInstanceService); + jest.spyOn(federatedInstanceService, 'fetch').mockImplementation(() => new Promise(() => {})); + }); + describe('Parse minimum object', () => { const host = 'https://host1.test'; const preferredUsername = `${rndstr('A-Z', 4)}${rndstr('a-z', 4)}`; @@ -28,13 +59,9 @@ describe('ActivityPub', () => { }; test('Minimum Actor', async () => { - const { MockResolver } = await import('../misc/mock-resolver.js'); - const { createPerson } = await import('../../src/activitypub/models/person.js'); - - const resolver = new MockResolver(); resolver._register(actor.id, actor); - const user = await createPerson(actor.id, resolver); + const user = await personService.createPerson(actor.id, resolver); assert.deepStrictEqual(user.uri, actor.id); assert.deepStrictEqual(user.username, actor.preferredUsername); @@ -42,14 +69,10 @@ describe('ActivityPub', () => { }); test('Minimum Note', async () => { - const { MockResolver } = await import('../misc/mock-resolver.js'); - const { createNote } = await import('../../src/activitypub/models/note.js'); - - const resolver = new MockResolver(); resolver._register(actor.id, actor); resolver._register(post.id, post); - const note = await createNote(post.id, resolver, true); + const note = await noteService.createNote(post.id, resolver, true); assert.deepStrictEqual(note?.uri, post.id); assert.deepStrictEqual(note.visibility, 'public'); @@ -75,13 +98,9 @@ describe('ActivityPub', () => { }; test('Actor', async () => { - const { MockResolver } = await import('../misc/mock-resolver.js'); - const { createPerson } = await import('../../src/activitypub/models/person.js'); - - const resolver = new MockResolver(); resolver._register(actor.id, actor); - const user = await createPerson(actor.id, resolver); + const user = await personService.createPerson(actor.id, resolver); assert.deepStrictEqual(user.name, actor.name.substr(0, 128)); }); From 0c59dd3da7c4fd065bfb46f1849f8b011959e1c4 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 19 Feb 2023 17:49:55 +0900 Subject: [PATCH 05/24] Update about-misskey.vue --- packages/frontend/src/pages/about-misskey.vue | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue index bc3d2481933a..b5f38d1c6f2e 100644 --- a/packages/frontend/src/pages/about-misskey.vue +++ b/packages/frontend/src/pages/about-misskey.vue @@ -111,6 +111,9 @@ const patronsWithIcon = [{ }, { name: 'だれかさん', icon: 'https://misskey-hub.net/patrons/f7409b5e5a88477a9b9d740c408de125.jpg', +}, { + name: 'narazaka', + icon: 'https://misskey-hub.net/patrons/e3affff31ffb4877b1196c7360abc3e5.jpg', }]; const patrons = [ From 7ce0f79f7fd77f1417ab597bfac8b80f2c6bbcde Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 19 Feb 2023 17:50:14 +0900 Subject: [PATCH 06/24] chore(server): tweak notes/featured api --- packages/backend/src/server/api/endpoints/notes/featured.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts index 8eff8fdb2225..26f69373d1d9 100644 --- a/packages/backend/src/server/api/endpoints/notes/featured.ts +++ b/packages/backend/src/server/api/endpoints/notes/featured.ts @@ -68,7 +68,7 @@ export default class extends Endpoint { let notes = await query .orderBy('note.score', 'DESC') - .take(ps.limit) + .take(50) .getMany(); notes.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); From 47b6f466ec0031478a246a02e8dd2bab156fb0da Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 19 Feb 2023 19:54:19 +0900 Subject: [PATCH 07/24] enhance(client): snap scroll on deck --- packages/frontend/src/ui/deck.vue | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue index ef29b2e72f11..4e9335959166 100644 --- a/packages/frontend/src/ui/deck.vue +++ b/packages/frontend/src/ui/deck.vue @@ -4,7 +4,7 @@
-
+
@@ -12,7 +12,7 @@ import { onMounted } from 'vue'; import XColumn from './column.vue'; import { updateColumn, Column } from './deck-store'; -import XTimeline from '@/components/MkTimeline.vue'; +import MkTimeline from '@/components/MkTimeline.vue'; import * as os from '@/os'; import { i18n } from '@/i18n'; @@ -26,7 +26,7 @@ const emit = defineEmits<{ (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); -let timeline = $shallowRef>(); +let timeline = $shallowRef>(); onMounted(() => { if (props.column.antennaId == null) { diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue index 169d2c4056dd..4c6b41e42e64 100644 --- a/packages/frontend/src/ui/deck/channel-column.vue +++ b/packages/frontend/src/ui/deck/channel-column.vue @@ -8,7 +8,7 @@
- + @@ -17,7 +17,7 @@ import { } from 'vue'; import XColumn from './column.vue'; import { updateColumn, Column } from './deck-store'; -import XTimeline from '@/components/MkTimeline.vue'; +import MkTimeline from '@/components/MkTimeline.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os'; import { i18n } from '@/i18n'; @@ -32,7 +32,7 @@ const emit = defineEmits<{ (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); -let timeline = $shallowRef>(); +let timeline = $shallowRef>(); if (props.column.channelId == null) { setChannel(); diff --git a/packages/frontend/src/ui/deck/direct-column.vue b/packages/frontend/src/ui/deck/direct-column.vue index 75b018cacdb6..15b76c4d92bc 100644 --- a/packages/frontend/src/ui/deck/direct-column.vue +++ b/packages/frontend/src/ui/deck/direct-column.vue @@ -2,15 +2,15 @@ - + @@ -38,6 +44,11 @@ const props = defineProps<{ margin-right: 8px; } +.badge { + height: 1.3em; + vertical-align: -20%; +} + .name { font-weight: bold; } diff --git a/packages/frontend/src/components/MkUserList.vue b/packages/frontend/src/components/MkUserList.vue index dd683fcc230d..51eb426e97d2 100644 --- a/packages/frontend/src/components/MkUserList.vue +++ b/packages/frontend/src/components/MkUserList.vue @@ -7,9 +7,9 @@
-