Files
cinny/src/swReply.test.ts
T
jaredandClaude Opus 5 d4420905e6
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
feat(notifications): inline reply from a browser notification (#203)
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
2026-09-19 14:12:23 -04:00

59 lines
2.0 KiB
TypeScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { buildReplyContent, replySendUrl, sendNotificationReply } from './swReply';
test('thread replies carry the m.thread relation, room replies do not', () => {
assert.deepEqual(buildReplyContent('hi'), { msgtype: 'm.text', body: 'hi', 'm.mentions': {} });
assert.deepEqual(buildReplyContent('hi', '$root')['m.relates_to'], {
rel_type: 'm.thread',
event_id: '$root',
is_falling_back: true,
'm.in_reply_to': { event_id: '$root' },
});
});
test('send URL encodes the room id against the session base URL', () => {
assert.equal(
replySendUrl('https://matrix.example.org/', '!r:example.org', 'tx1'),
'https://matrix.example.org/_matrix/client/v3/rooms/!r%3Aexample.org/send/m.room.message/tx1',
);
});
test('sendNotificationReply PUTs with the bearer token and reports ok / failure', async () => {
const calls: { url: string; init: RequestInit }[] = [];
const fetchOk = (async (url: string, init: RequestInit) => {
calls.push({ url, init });
return { ok: true } as Response;
}) as unknown as typeof fetch;
assert.equal(
await sendNotificationReply(
{ accessToken: 'tok', baseUrl: 'https://hs' },
'!r:hs',
'$t',
'yo',
fetchOk,
),
true,
);
assert.equal(calls[0].init.method, 'PUT');
assert.equal((calls[0].init.headers as Record<string, string>).Authorization, 'Bearer tok');
assert.match(
calls[0].url,
/^https:\/\/hs\/_matrix\/client\/v3\/rooms\/!r%3Ahs\/send\/m\.room\.message\/swreply-/,
);
assert.equal(JSON.parse(calls[0].init.body as string)['m.relates_to'].event_id, '$t');
const fetchFail = (async () => ({ ok: false }) as Response) as unknown as typeof fetch;
assert.equal(
await sendNotificationReply(
{ accessToken: 'tok', baseUrl: 'https://hs' },
'!r:hs',
undefined,
'yo',
fetchFail,
),
false,
);
assert.equal(await sendNotificationReply(undefined, '!r:hs', undefined, 'yo', fetchOk), false);
});