mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
fix: preview the named message when the inbox lookups beside it fail #1041
Any failed method in the preview request answered 502, so when the server named the delivered message and it was read fine, a failing Inbox query still turned the notification into a generic "New mail". The named message is now a complete preview on its own. A response without the optional `total` was also treated as a failure; it now counts the returned ids.
This commit is contained in:
@@ -247,15 +247,17 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
// Missing/failed method responses are preview failures, not proof that
|
||||
// there is no unread mail. A non-2xx response lets the SW show its fallback.
|
||||
for (const call of methodCalls as [string, unknown, string][]) {
|
||||
const response = data.methodResponses.find(([method, , callId]) =>
|
||||
method === call[0] && callId === call[2]);
|
||||
const result = response?.[1];
|
||||
if (!result || (call[0] === 'Email/query'
|
||||
? !Array.isArray(result.ids) || typeof result.total !== 'number'
|
||||
: !Array.isArray(result.list))) {
|
||||
return NextResponse.json({ error: 'JMAP email query failed' }, { status: 502 });
|
||||
}
|
||||
// The message the server named, once read, is a complete preview on its
|
||||
// own, whatever became of the Inbox lookups next to it.
|
||||
const resultOf = (name: string, id: string) =>
|
||||
data.methodResponses.find(([method, , callId]) => method === name && callId === id)?.[1];
|
||||
const failed = (methodCalls as [string, unknown, string][]).some(([name, , id]) => {
|
||||
const result = resultOf(name, id);
|
||||
return !result || (name === 'Email/query' ? !Array.isArray(result.ids) : !Array.isArray(result.list));
|
||||
});
|
||||
const deliveredList = resultOf('Email/get', 'delivered')?.list;
|
||||
if (failed && !(Array.isArray(deliveredList) && deliveredList.length > 0)) {
|
||||
return NextResponse.json({ error: 'JMAP email query failed' }, { status: 502 });
|
||||
}
|
||||
|
||||
type EmailLite = {
|
||||
@@ -272,7 +274,9 @@ export async function GET(request: NextRequest) {
|
||||
let unreadTotal = 0;
|
||||
for (const [method, body, callId] of data.methodResponses) {
|
||||
if (method === 'Email/query') {
|
||||
unreadTotal = ((body as { total?: number }).total) ?? 0;
|
||||
// A server that leaves out the optional total still lists the ids.
|
||||
const query = body as { total?: number; ids?: unknown[] };
|
||||
unreadTotal = typeof query.total === 'number' ? query.total : (query.ids?.length ?? 0);
|
||||
}
|
||||
if (method === 'Email/get') {
|
||||
const list = (body as { list?: EmailLite[] }).list ?? [];
|
||||
|
||||
@@ -137,7 +137,6 @@ describe('push preview JMAP failures', () => {
|
||||
it.each([
|
||||
[['error', { type: 'serverFail' }, 'eq'], ['error', { type: 'resultReference' }, 'eg']],
|
||||
[['Email/query', { ids: ['e1'], total: 1 }, 'eq'], ['error', { type: 'serverFail' }, 'eg']],
|
||||
[['Email/query', { ids: [] }, 'eq'], ['Email/get', { list: [] }, 'eg']],
|
||||
])('does not report zero unread on an email method failure', async (query, get) => {
|
||||
fetchJmapServer.mockReset()
|
||||
.mockResolvedValueOnce(jsonResponse(SESSION))
|
||||
@@ -156,6 +155,32 @@ describe('push preview JMAP failures', () => {
|
||||
] }));
|
||||
expect(await callRoute('a')).toEqual({ status: 200, body: { email: null, unreadTotal: 0, slot: 0 } });
|
||||
});
|
||||
|
||||
// `total` is optional in a JMAP Email/query response.
|
||||
it('counts the ids when the server leaves out the total', async () => {
|
||||
fetchJmapServer.mockReset()
|
||||
.mockResolvedValueOnce(jsonResponse(SESSION))
|
||||
.mockResolvedValueOnce(jsonResponse({ methodResponses: [['Mailbox/query', { ids: ['inbox'] }, 'mb']] }))
|
||||
.mockResolvedValueOnce(jsonResponse({ methodResponses: [
|
||||
['Email/query', { ids: [] }, 'eq'],
|
||||
['Email/get', { list: [] }, 'eg'],
|
||||
] }));
|
||||
expect(await callRoute('a')).toEqual({ status: 200, body: { email: null, unreadTotal: 0, slot: 0 } });
|
||||
});
|
||||
|
||||
it('previews the named message even when the Inbox lookups fail', async () => {
|
||||
fetchJmapServer.mockReset()
|
||||
.mockResolvedValueOnce(jsonResponse(SESSION))
|
||||
.mockResolvedValueOnce(jsonResponse({ methodResponses: [['Mailbox/query', { ids: ['inbox'] }, 'mb']] }))
|
||||
.mockResolvedValueOnce(jsonResponse({ methodResponses: [
|
||||
['error', { type: 'serverFail' }, 'eq'],
|
||||
['error', { type: 'resultReference' }, 'eg'],
|
||||
['Email/get', { list: [{ id: 'd', threadId: 'td' }] }, 'delivered'],
|
||||
] }));
|
||||
expect(await callRoute('a', 'd')).toEqual({
|
||||
status: 200, body: { email: { id: 'd', threadId: 'td' }, unreadTotal: 1, slot: 0 },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user