feat(notifications): inline reply from a browser notification (#203)
CI / Build & Quality Checks (push) Successful in 1m38s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 7s
CI / Trigger Desktop Build (push) Successful in 9s
CI / Playwright smoke (e2e) (push) Successful in 2m50s
CI / Build & Quality Checks (push) Successful in 1m38s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 7s
CI / Trigger Desktop Build (push) Successful in 9s
CI / Playwright smoke (e2e) (push) Successful in 2m50s
Message notifications shown through the service worker now carry a text-input
'Reply' action (Chrome desktop/Android). On notificationclick with
action==='reply' the SW sends the typed text itself — it already holds the
newest session's access token for authenticated media — as m.room.message
(threaded when the notification was for a thread), so it works with the tab in
the background or closed; a failed send shows a 'Reply not sent' notification
that opens the room. Not offered for encrypted rooms (the SW cannot encrypt).
The sender lives in swReply.ts so it is unit-tested; verified headless that
the SW notification carries actions + {roomId, threadId}.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
@@ -510,7 +510,19 @@ function MessageNotifications() {
|
||||
// For thread replies widen the tag to room:thread so each thread
|
||||
// coalesces independently instead of clobbering the room's bucket.
|
||||
tag: threadId ? `${roomId}:${threadId}` : roomId,
|
||||
data: { path: roomPath },
|
||||
// [Gitea #203] Inline reply from the notification (Chrome/Android
|
||||
// text-input actions). The SW posts the reply itself — it holds the
|
||||
// access token for authenticated media already — so this works with
|
||||
// the tab in the background or closed. Not offered for encrypted
|
||||
// rooms: the SW cannot encrypt.
|
||||
...(encrypted
|
||||
? {}
|
||||
: {
|
||||
// `actions` is SW-notification-only and not in lib.dom's
|
||||
// NotificationOptions typing.
|
||||
actions: [{ action: 'reply', title: 'Reply', type: 'text', placeholder: 'Reply…' }],
|
||||
}),
|
||||
data: { path: roomPath, roomId, threadId: threadId || undefined },
|
||||
},
|
||||
() => {
|
||||
window.focus();
|
||||
|
||||
@@ -278,7 +278,11 @@ const isDesktopApp = (): boolean =>
|
||||
|
||||
export const showOsNotification = async (
|
||||
title: string,
|
||||
options: NotificationOptions & { data?: { path?: string } },
|
||||
options: NotificationOptions & {
|
||||
data?: { path?: string; roomId?: string; threadId?: string };
|
||||
/** Chrome/Android SW notifications: buttons, incl. text-input replies. */
|
||||
actions?: { action: string; title: string; type?: 'button' | 'text'; placeholder?: string }[];
|
||||
},
|
||||
onClick?: () => void,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user