Compare commits

...

5 commits

Author SHA1 Message Date
45ee9951dc
client: remove unused notification-toast component 2022-11-12 21:22:36 +01:00
cd87e262fe
client: pass along notifications if push notifs disabled 2022-11-12 20:53:37 +01:00
23953b9ad1
service worker: refactor message event handler
It is now possible for the client to trigger notifications "manually"
if push notifications are not configured on the server.
2022-11-12 20:51:42 +01:00
db0e6a241c
service worker: also show notifications if client is connected 2022-11-12 18:52:12 +01:00
0c3c855d29
client: translate comments 2022-11-12 18:52:11 +01:00
4 changed files with 20 additions and 104 deletions

View file

@ -1,67 +0,0 @@
<template>
<div class="mk-notification-toast" :style="{ zIndex }">
<transition :name="$store.state.animation ? 'notification-toast' : ''" appear @after-leave="emit('closed')">
<XNotification v-if="showing" :notification="notification" class="notification _panel"/>
</transition>
</div>
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import XNotification from './notification.vue';
import * as os from '@/os';
defineProps<{
notification: any; // TODO
}>();
const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const zIndex = os.claimZIndex('high');
let showing = $ref(true);
onMounted(() => {
window.setTimeout(() => {
showing = false;
}, 6000);
});
</script>
<style lang="scss" scoped>
.notification-toast-enter-active, .notification-toast-leave-active {
transition: opacity 0.3s, transform 0.3s !important;
}
.notification-toast-enter-from, .notification-toast-leave-to {
opacity: 0;
transform: translateX(-250px);
}
.mk-notification-toast {
position: fixed;
left: 0;
width: 250px;
top: 32px;
padding: 0 32px;
pointer-events: none;
@media (max-width: 700px) {
top: initial;
bottom: 112px;
padding: 0 16px;
}
@media (max-width: 500px) {
bottom: calc(env(safe-area-inset-bottom, 0px) + 92px);
padding: 0 8px;
}
> .notification {
height: 100%;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
border-radius: 8px;
overflow: hidden;
}
}
</style>

View file

@ -9,7 +9,7 @@ export async function initializeSw() {
navigator.serviceWorker.register('/sw.js', { scope: '/', type: 'classic' }); navigator.serviceWorker.register('/sw.js', { scope: '/', type: 'classic' });
navigator.serviceWorker.ready.then(registration => { navigator.serviceWorker.ready.then(registration => {
registration.active?.postMessage({ registration.active?.postMessage({
msg: 'initialize', type: 'initialize',
lang, lang,
}); });
@ -33,14 +33,14 @@ export async function initializeSw() {
}) })
// When subscribe failed // When subscribe failed
.catch(async (err: Error) => { .catch(async (err: Error) => {
// 通知が許可されていなかったとき // when notifications were not authorized
if (err.name === 'NotAllowedError') { if (err.name === 'NotAllowedError') {
return; return;
} }
// 違うapplicationServerKey (または gcm_sender_id)のサブスクリプションが // The error may have been caused by the fact that a subscription to a
// 既に存在していることが原因でエラーになった可能性があるので、 // different applicationServerKey (or gcm_sender_id) already exists, so
// そのサブスクリプションを解除しておく // unsubscribe to it.
const subscription = await registration.pushManager.getSubscription(); const subscription = await registration.pushManager.getSubscription();
if (subscription) subscription.unsubscribe(); if (subscription) subscription.unsubscribe();
}); });

View file

@ -19,6 +19,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent, Ref, ref } from 'vue'; import { defineAsyncComponent, Ref, ref } from 'vue';
import { swInject } from './sw-inject'; import { swInject } from './sw-inject';
import { instance } from '@/instance';
import { popup as showPopup, popups, pendingApiRequestsCount } from '@/os'; import { popup as showPopup, popups, pendingApiRequestsCount } from '@/os';
import { uploads } from '@/scripts/upload'; import { uploads } from '@/scripts/upload';
import * as sound from '@/scripts/sound'; import * as sound from '@/scripts/sound';
@ -32,14 +33,12 @@ const dev: Ref<boolean> = ref(_DEV_);
const onNotification = (notification: { type: string; id: any; }): void => { const onNotification = (notification: { type: string; id: any; }): void => {
if ($i?.mutingNotificationTypes.includes(notification.type)) return; if ($i?.mutingNotificationTypes.includes(notification.type)) return;
if (document.visibilityState === 'visible') { // if push notifications are enabled there is no need to pass the notification along
stream.send('readNotification', { if (!instance.enableServiceWorker) {
id: notification.id, // service worker is not enabled or set up on the server, pass the notification along
navigator.serviceWorker.ready.then(registration => {
registration.active.postMessage({ type: 'notification', body: notification });
}); });
showPopup(defineAsyncComponent(() => import('@/components/notification-toast.vue')), {
notification,
}, {}, 'closed');
} }
sound.play('notification'); sound.play('notification');

View file

@ -24,19 +24,13 @@ self.addEventListener('activate', ev => {
}); });
self.addEventListener('push', ev => { self.addEventListener('push', ev => {
// クライアント取得 ev.waitUntil((async <K extends keyof pushNotificationDataMap>() => {
ev.waitUntil(self.clients.matchAll({
includeUncontrolled: true,
type: 'window'
}).then(async <K extends keyof pushNotificationDataMap>(clients: readonly WindowClient[]) => {
const data: pushNotificationDataMap[K] = ev.data?.json(); const data: pushNotificationDataMap[K] = ev.data?.json();
switch (data.type) { switch (data.type) {
// case 'driveFileCreated': // case 'driveFileCreated':
case 'notification': case 'notification':
case 'unreadMessagingMessage': case 'unreadMessagingMessage':
// クライアントがあったらストリームに接続しているということなので通知しない
if (clients.length != 0) return;
return createNotification(data); return createNotification(data);
case 'readAllNotifications': case 'readAllNotifications':
for (const n of await self.registration.getNotifications()) { for (const n of await self.registration.getNotifications()) {
@ -69,7 +63,7 @@ self.addEventListener('push', ev => {
} }
return createEmptyNotification(); return createEmptyNotification();
})); })());
}); });
self.addEventListener('notificationclick', <K extends keyof pushNotificationDataMap>(ev: ServiceWorkerGlobalScopeEventMap['notificationclick']) => { self.addEventListener('notificationclick', <K extends keyof pushNotificationDataMap>(ev: ServiceWorkerGlobalScopeEventMap['notificationclick']) => {
@ -169,24 +163,14 @@ self.addEventListener('notificationclose', <K extends keyof pushNotificationData
self.addEventListener('message', (ev: ServiceWorkerGlobalScopeEventMap['message']) => { self.addEventListener('message', (ev: ServiceWorkerGlobalScopeEventMap['message']) => {
ev.waitUntil((async () => { ev.waitUntil((async () => {
switch (ev.data) {
case 'clear':
// Cache Storage全削除
await caches.keys()
.then(cacheNames => Promise.all(
cacheNames.map(name => caches.delete(name))
));
return; // TODO
}
if (typeof ev.data === 'object') { if (typeof ev.data === 'object') {
// E.g. '[object Array]' → 'array' switch (ev.data.type) {
const otype = Object.prototype.toString.call(ev.data).slice(8, -1).toLowerCase(); case 'initialize':
if (otype === 'object') {
if (ev.data.msg === 'initialize') {
swLang.setLang(ev.data.lang); swLang.setLang(ev.data.lang);
} break;
case 'notification':
createNotification(ev.data);
break;
} }
} }
})()); })());