Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: テスト通知を送信できるようにする #11810

Merged
merged 10 commits into from
Sep 11, 2023
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
- 投稿フォームのプレビューの表示状態を記憶するように
- AiScriptからMisskeyサーバーAPIを呼び出す際の制限を撤廃
- Playで直接投稿フォームを埋め込めるように(`Ui:C:postForm`)
- 通知をテストできるように
- Enhance: ユーザーメニューでスイッチでユーザーリストに追加・削除できるように
- Enhance: 自分が押したリアクションのデザインを改善
- Enhance: ノート検索にローカルのみ検索可能なオプションの追加
Expand Down
4 changes: 4 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2132,6 +2132,10 @@ export interface Locale {
"unreadAntennaNote": string;
"emptyPushNotificationMessage": string;
"achievementEarned": string;
"testNotification": string;
"checkNotificationBehavior": string;
"sendTestNotification": string;
"notificationWillBeDisplayedLikeThis": string;
"_types": {
"all": string;
"follow": string;
Expand Down
4 changes: 4 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2047,6 +2047,10 @@ _notification:
unreadAntennaNote: "アンテナ {name}"
emptyPushNotificationMessage: "プッシュ通知の更新をしました"
achievementEarned: "実績を獲得"
testNotification: "通知テスト"
checkNotificationBehavior: "通知の表示を確かめる"
sendTestNotification: "テスト通知を送信する"
notificationWillBeDisplayedLikeThis: "通知はこのように表示されます"

_types:
all: "すべて"
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/models/entities/Notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export type MiNotification = {
* followRequestAccepted - 自分の送ったフォローリクエストが承認された
* achievementEarned - 実績を獲得
* app - アプリ通知
* test - テスト通知(サーバー側)
*/
type: typeof notificationTypes[number];

Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/server/api/EndpointsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
import * as ep___notifications_create from './endpoints/notifications/create.js';
import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
import * as ep___notifications_testNotification from './endpoints/notifications/test-notification.js';
import * as ep___pagePush from './endpoints/page-push.js';
import * as ep___pages_create from './endpoints/pages/create.js';
import * as ep___pages_delete from './endpoints/pages/delete.js';
Expand Down Expand Up @@ -629,6 +630,7 @@ const $notes_unrenote: Provider = { provide: 'ep:notes/unrenote', useClass: ep__
const $notes_userListTimeline: Provider = { provide: 'ep:notes/user-list-timeline', useClass: ep___notes_userListTimeline.default };
const $notifications_create: Provider = { provide: 'ep:notifications/create', useClass: ep___notifications_create.default };
const $notifications_markAllAsRead: Provider = { provide: 'ep:notifications/mark-all-as-read', useClass: ep___notifications_markAllAsRead.default };
const $notifications_testNotification: Provider = { provide: 'ep:notifications/test-notification', useClass: ep___notifications_testNotification.default };
const $pagePush: Provider = { provide: 'ep:page-push', useClass: ep___pagePush.default };
const $pages_create: Provider = { provide: 'ep:pages/create', useClass: ep___pages_create.default };
const $pages_delete: Provider = { provide: 'ep:pages/delete', useClass: ep___pages_delete.default };
Expand Down Expand Up @@ -979,6 +981,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$notes_userListTimeline,
$notifications_create,
$notifications_markAllAsRead,
$notifications_testNotification,
$pagePush,
$pages_create,
$pages_delete,
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/server/api/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
import * as ep___notifications_create from './endpoints/notifications/create.js';
import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
import * as ep___notifications_testNotification from './endpoints/notifications/test-notification.js';
import * as ep___pagePush from './endpoints/page-push.js';
import * as ep___pages_create from './endpoints/pages/create.js';
import * as ep___pages_delete from './endpoints/pages/delete.js';
Expand Down Expand Up @@ -627,6 +628,7 @@ const eps = [
['notes/user-list-timeline', ep___notes_userListTimeline],
['notifications/create', ep___notifications_create],
['notifications/mark-all-as-read', ep___notifications_markAllAsRead],
['notifications/test-notification', ep___notifications_testNotification],
['page-push', ep___pagePush],
['pages/create', ep___pages_create],
['pages/delete', ep___pages_delete],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/

import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { NotificationService } from '@/core/NotificationService.js';

export const meta = {
tags: ['notifications'],

requireCredential: true,

kind: 'write:notifications',
} as const;

export const paramDef = {
type: 'object',
properties: {},
required: [],
} as const;

@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private notificationService: NotificationService,
) {
super(meta, paramDef, async (ps, user) => {
this.notificationService.createNotification(user.id, 'test', {});
});
}
}
2 changes: 1 addition & 1 deletion packages/backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/

export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app'] as const;
export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app', 'test'] as const;
export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const;

export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;
Expand Down
4 changes: 4 additions & 0 deletions packages/frontend/src/components/MkNotification.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.head">
<MkAvatar v-if="notification.type === 'pollEnded'" :class="$style.icon" :user="notification.note.user" link preview/>
<MkAvatar v-else-if="notification.type === 'achievementEarned'" :class="$style.icon" :user="$i" link preview/>
<img v-else-if="notification.type === 'test'" :class="$style.icon" :src="infoImageUrl"/>
<MkAvatar v-else-if="notification.user" :class="$style.icon" :user="notification.user" link preview/>
<img v-else-if="notification.icon" :class="$style.icon" :src="notification.icon" alt=""/>
<div
Expand Down Expand Up @@ -47,6 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<header :class="$style.header">
<span v-if="notification.type === 'pollEnded'">{{ i18n.ts._notification.pollEnded }}</span>
<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
<MkA v-else-if="notification.user" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
<span v-else>{{ notification.header }}</span>
<MkTime v-if="withTime" :time="notification.createdAt" :class="$style.headerTime"/>
Expand Down Expand Up @@ -91,6 +93,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton :class="$style.followRequestCommandButton" rounded danger @click="rejectFollowRequest()"><i class="ti ti-x"/> {{ i18n.ts.reject }}</MkButton>
</div>
</template>
<span v-else-if="notification.type === 'test'" :class="$style.text">{{ i18n.ts._notification.notificationWillBeDisplayedLikeThis }}</span>
<span v-else-if="notification.type === 'app'" :class="$style.text">
<Mfm :text="notification.body" :nowrap="false"/>
</span>
Expand All @@ -113,6 +116,7 @@ import { i18n } from '@/i18n';
import * as os from '@/os';
import { useTooltip } from '@/scripts/use-tooltip';
import { $i } from '@/account';
import { infoImageUrl } from '@/instance';

const props = withDefaults(defineProps<{
notification: Misskey.entities.Notification;
Expand Down
3 changes: 3 additions & 0 deletions packages/frontend/src/pages/settings/general.vue
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<option value="vertical"><i class="ti ti-carousel-vertical"></i> {{ i18n.ts.vertical }}</option>
<option value="horizontal"><i class="ti ti-carousel-horizontal"></i> {{ i18n.ts.horizontal }}</option>
</MkRadios>

<MkButton @click="testNotification('client')">{{ i18n.ts._notification.checkNotificationBehavior }}</MkButton>
</div>
</FormSection>

Expand Down Expand Up @@ -190,6 +192,7 @@ import { unisonReload } from '@/scripts/unison-reload';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { miLocalStorage } from '@/local-storage';
import { testNotification } from '@/scripts/test-notification';

const lang = ref(miLocalStorage.getItem('lang'));
const fontSize = ref(miLocalStorage.getItem('fontSize'));
Expand Down
6 changes: 6 additions & 0 deletions packages/frontend/src/pages/settings/notifications.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<FormLink @click="readAllUnreadNotes">{{ i18n.ts.markAsReadAllUnreadNotes }}</FormLink>
</div>
</FormSection>
<FormSection>
<div class="_gaps_m">
<FormLink @click="testNotification('server')">{{ i18n.ts._notification.sendTestNotification }}</FormLink>
</div>
</FormSection>
<FormSection>
<template #label>{{ i18n.ts.pushNotification }}</template>

Expand Down Expand Up @@ -41,6 +46,7 @@ import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue';
import { notificationTypes } from '@/const';
import { testNotification } from '@/scripts/test-notification';

let allowButton = $shallowRef<InstanceType<typeof MkPushNotificationAllowButton>>();
let pushRegistrationInServer = $computed(() => allowButton?.pushRegistrationInServer);
Expand Down
34 changes: 34 additions & 0 deletions packages/frontend/src/scripts/test-notification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/

import * as Misskey from 'misskey-js';
import * as os from '@/os';
import { globalEvents } from '@/events';

/**
* テスト通知を送信
*
* - `client` … 通知ポップアップのみを表示
* - `server` … サーバー側から通知を送信
*
* @param type 通知タイプを指定
*/
export function testNotification(type: 'client' | 'server'): void {
const notification: Misskey.entities.Notification = {
id: Math.random().toString(),
createdAt: new Date().toUTCString(),
isRead: false,
type: 'test',
};

switch (type) {
case 'server':
os.api('notifications/test-notification');
break;
case 'client':
globalEvents.emit('clientNotification', notification);
break;
}
}
8 changes: 6 additions & 2 deletions packages/frontend/src/ui/_common_/common.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import { $i } from '@/account';
import { useStream } from '@/stream';
import { i18n } from '@/i18n';
import { defaultStore } from '@/store';
import { globalEvents } from '@/events';

const XStreamIndicator = defineAsyncComponent(() => import('./stream-indicator.vue'));
const XUpload = defineAsyncComponent(() => import('./upload.vue'));
Expand All @@ -64,11 +65,13 @@ const dev = _DEV_;

let notifications = $ref<Misskey.entities.Notification[]>([]);

function onNotification(notification) {
function onNotification(notification: Misskey.entities.Notification, isClient: boolean = false) {
if ($i.mutingNotificationTypes.includes(notification.type)) return;

if (document.visibilityState === 'visible') {
useStream().send('readNotification');
if (!isClient) {
useStream().send('readNotification');
}

notifications.unshift(notification);
window.setTimeout(() => {
Expand All @@ -86,6 +89,7 @@ function onNotification(notification) {
if ($i) {
const connection = useStream().useChannel('main', null, 'UI');
connection.on('notification', onNotification);
globalEvents.on('clientNotification', notification => onNotification(notification, true));

//#region Listen message from SW
if ('serviceWorker' in navigator) {
Expand Down
8 changes: 7 additions & 1 deletion packages/misskey-js/etc/misskey-js.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1915,6 +1915,10 @@ export type Endpoints = {
};
res: null;
};
'notifications/test-notification': {
req: NoParams;
res: null;
};
'notifications/mark-all-as-read': {
req: NoParams;
res: null;
Expand Down Expand Up @@ -2627,6 +2631,8 @@ type Notification_2 = {
header?: string | null;
body: string;
icon?: string | null;
} | {
type: 'test';
});

// @public (undocumented)
Expand Down Expand Up @@ -2842,7 +2848,7 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u
//
// src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts
// src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts
// src/api.types.ts:630:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
// src/api.types.ts:631:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
// src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts

// (No @packageDocumentation comment for this package)
Expand Down
1 change: 1 addition & 0 deletions packages/misskey-js/src/api.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,7 @@ export type Endpoints = {

// notifications
'notifications/create': { req: { body: string; header?: string | null; icon?: string | null; }; res: null; };
'notifications/test-notification': { req: NoParams; res: null; };
'notifications/mark-all-as-read': { req: NoParams; res: null; };

// page-push
Expand Down
2 changes: 2 additions & 0 deletions packages/misskey-js/src/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ export type Notification = {
header?: string | null;
body: string;
icon?: string | null;
} | {
type: 'test';
});

export type MessagingMessage = {
Expand Down