From 50165bd3b71f1d0596b7a23d535dc1d71739b85a Mon Sep 17 00:00:00 2001 From: ttipo Date: Wed, 28 Oct 2026 13:32:09 +0900 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20=EC=8B=A4=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=20=EC=83=88=20=EB=A9=94=EC=9D=BC=20=C2=B7=20=EB=B0=94=ED=83=95?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EC=95=8C=EB=A6=BC=20=C2=B7=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=E2=80=BA=20=EC=95=8C=EB=A6=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그인한 동안 앱 전체에서 실시간 연결 — 브라우저마다 하나(Web Locks 잠금 + BroadcastChannel 로 다른 탭에 나눔), fetch 로 읽기, 401 은 세션 갱신 한 번, 자리 잡은 뒤에만 다시 잇기 간격을 처음부터(열리자마자 끝나는 연결에서 갱신 폭주 막기), 탭을 닫으면 이어받기 - 메일 화면: 연결이 살아 있는 동안 1분 확인을 멈추고, 바뀌면 1초 모아 맞추기 · 숨은 탭은 탭 제목 (N) 만 - 바탕화면 알림: 웹메일을 보고 있지 않을 때만(보이고 초점이 있는 탭 없음), 한 통 · 여러 통 · 제목 숨김 문구, 누르면 그 메일로, 돌아오면 닫기 - 설정 › 메일 › 알림(허락 상태 · 제목 숨김 · 시험 알림 · 연결 상태)과 목록 위 한 번 권하기 줄, 알림 아이콘 Co-Authored-By: Claude Opus 5.5 --- frontend/src/App.vue | 9 + .../src/assets/brand/notification-icon.png | Bin 0 -> 8463 bytes .../src/components/feedback/ToastHost.vue | 17 +- .../src/components/mail/MailListFrame.spec.ts | 45 +- .../src/components/mail/MailListFrame.vue | 18 +- .../components/mail/MailNotifyOffer.spec.ts | 172 ++++ .../src/components/mail/MailNotifyOffer.vue | 170 ++++ .../settings/NotifySettingsForm.vue | 281 ++++++ .../settings/RealtimeLinkStatus.vue | 101 ++ .../components/settings/SettingsPageFrame.vue | 3 + frontend/src/composables/useApi.ts | 7 +- .../src/composables/useDesktopNotification.ts | 74 ++ frontend/src/composables/useMailNavigation.ts | 14 + .../src/composables/useMailRealtime.spec.ts | 889 ++++++++++++++++++ frontend/src/composables/useMailRealtime.ts | 545 +++++++++++ .../composables/useMailStatePolling.spec.ts | 155 ++- .../src/composables/useMailStatePolling.ts | 102 +- .../src/composables/useMailTabTitle.spec.ts | 21 + .../composables/useNewMailNotifier.spec.ts | 277 ++++++ .../src/composables/useNewMailNotifier.ts | 75 ++ frontend/src/composables/useToast.ts | 10 +- frontend/src/pages/mail/MailLayout.vue | 7 +- .../src/pages/settings/MailNotifyPage.spec.ts | 247 +++++ .../src/pages/settings/MailNotifyPage.vue | 92 ++ .../src/pages/settings/MailSignaturePage.vue | 3 + .../src/pages/settings/SettingsLayout.spec.ts | 6 +- .../src/pages/settings/SettingsLayout.vue | 2 +- frontend/src/router/routes.ts | 8 +- frontend/src/shared/constants/icons.ts | 4 + frontend/src/shared/constants/mail-notify.ts | 111 +++ frontend/src/shared/constants/mail.ts | 2 +- .../src/shared/constants/settings-menu.ts | 7 +- .../src/shared/utils/browser-support.spec.ts | 43 + frontend/src/shared/utils/browser-support.ts | 31 + .../src/shared/utils/desktop-notify.spec.ts | 166 ++++ frontend/src/shared/utils/desktop-notify.ts | 142 +++ frontend/src/shared/utils/mail-events.spec.ts | 87 ++ frontend/src/shared/utils/mail-events.ts | 110 +++ .../src/shared/utils/mail-realtime.spec.ts | 111 +++ frontend/src/shared/utils/mail-realtime.ts | 148 +++ frontend/src/shared/utils/sse-parser.spec.ts | 111 +++ frontend/src/shared/utils/sse-parser.ts | 120 +++ frontend/src/stores/mail-notify.store.spec.ts | 181 ++++ frontend/src/stores/mail-notify.store.ts | 157 ++++ frontend/src/stores/mail-realtime.store.ts | 70 ++ frontend/src/stores/mail.store.ts | 1 + frontend/src/stores/mail/mailboxes.ts | 10 + frontend/src/styles/tokens.css | 8 + frontend/src/test-utils/event-stream.ts | 125 +++ frontend/src/test-utils/notification.ts | 76 ++ frontend/src/test-utils/router.ts | 6 + frontend/src/test-utils/web-locks.ts | 95 ++ 52 files changed, 5234 insertions(+), 38 deletions(-) create mode 100644 frontend/src/assets/brand/notification-icon.png create mode 100644 frontend/src/components/mail/MailNotifyOffer.spec.ts create mode 100644 frontend/src/components/mail/MailNotifyOffer.vue create mode 100644 frontend/src/components/settings/NotifySettingsForm.vue create mode 100644 frontend/src/components/settings/RealtimeLinkStatus.vue create mode 100644 frontend/src/composables/useDesktopNotification.ts create mode 100644 frontend/src/composables/useMailRealtime.spec.ts create mode 100644 frontend/src/composables/useMailRealtime.ts create mode 100644 frontend/src/composables/useNewMailNotifier.spec.ts create mode 100644 frontend/src/composables/useNewMailNotifier.ts create mode 100644 frontend/src/pages/settings/MailNotifyPage.spec.ts create mode 100644 frontend/src/pages/settings/MailNotifyPage.vue create mode 100644 frontend/src/shared/constants/mail-notify.ts create mode 100644 frontend/src/shared/utils/browser-support.spec.ts create mode 100644 frontend/src/shared/utils/browser-support.ts create mode 100644 frontend/src/shared/utils/desktop-notify.spec.ts create mode 100644 frontend/src/shared/utils/desktop-notify.ts create mode 100644 frontend/src/shared/utils/mail-events.spec.ts create mode 100644 frontend/src/shared/utils/mail-events.ts create mode 100644 frontend/src/shared/utils/mail-realtime.spec.ts create mode 100644 frontend/src/shared/utils/mail-realtime.ts create mode 100644 frontend/src/shared/utils/sse-parser.spec.ts create mode 100644 frontend/src/shared/utils/sse-parser.ts create mode 100644 frontend/src/stores/mail-notify.store.spec.ts create mode 100644 frontend/src/stores/mail-notify.store.ts create mode 100644 frontend/src/stores/mail-realtime.store.ts create mode 100644 frontend/src/test-utils/event-stream.ts create mode 100644 frontend/src/test-utils/notification.ts create mode 100644 frontend/src/test-utils/web-locks.ts diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 34e0e77..244a73b 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -5,10 +5,19 @@ import { RouterView } from 'vue-router' import SessionExpiredDialog from '@/components/feedback/SessionExpiredDialog.vue' import ToastHost from '@/components/feedback/ToastHost.vue' +import { useClock } from '@/composables/useClock' import { useLogoutSync } from '@/composables/useLogoutBroadcast' +import { useMailRealtime } from '@/composables/useMailRealtime' +import { useNewMailNotifier } from '@/composables/useNewMailNotifier' // 다른 탭에서 로그아웃하면 이 탭도 로그인 화면으로 간다 (계약 1절 "클라이언트 처리 규칙") useLogoutSync() + +// 실시간 새 메일 — 로그인한 동안 앱 전체에서 돈다(설정 등 다른 화면에서도 바탕화면 알림 · 연결 상태가 이어지게). +// 메일 화면은 이 소식을 받아 목록 · 개수를 맞춘다 (계약 2절 "실시간 알림" — MailLayout 의 useMailStatePolling). +// 사용자가 웹메일 탭(어느 탭이든)으로 돌아오면 띄운 바탕화면 알림을 닫는다 +const newMailNotifier = useNewMailNotifier() +useMailRealtime({ clock: useClock(), onNewMail: newMailNotifier.notify, onUserReturn: newMailNotifier.dismiss })