mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-06 08:07:23 +00:00
fix: show the new-mail notification when the connection kept from the foreground has died #94
This commit is contained in:
@@ -19,6 +19,7 @@ import {
|
||||
handleNotificationAction,
|
||||
matchAccountsForPush,
|
||||
parseRelayPushData,
|
||||
PUSH_REQUEST_TIMEOUT_MS,
|
||||
pushBackgroundTask,
|
||||
selectNotifiableEmails,
|
||||
senderFaviconsAllowed,
|
||||
@@ -268,6 +269,30 @@ describe('pushBackgroundTask notifications', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('shows the generic notification when a request never answers, as on a dead connection (#94)', async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
(secureFetch as ReturnType<typeof vi.fn>).mockImplementation(() => new Promise(() => undefined));
|
||||
let settled = false;
|
||||
const task = pushBackgroundTask({ kind: 'jmap-email-push', accountLabel: 'alice', accountId: 'jmap-primary', emailIds: '["m1"]' })
|
||||
.then(() => { settled = true; });
|
||||
|
||||
await vi.advanceTimersByTimeAsync(PUSH_REQUEST_TIMEOUT_MS - 1);
|
||||
expect(settled).toBe(false);
|
||||
expect(showNotification).not.toHaveBeenCalled();
|
||||
|
||||
await vi.advanceTimersByTimeAsync(1);
|
||||
await task;
|
||||
expect(showNotification).toHaveBeenCalledTimes(1);
|
||||
expect((showNotification.mock.calls[0] as unknown[])[0]).toMatchObject({ title: 'New email', body: 'alice', emailId: '' });
|
||||
// The request was told to stop, so it doesn't hold the connection.
|
||||
const init = (secureFetch as ReturnType<typeof vi.fn>).mock.calls[0][1] as { signal: AbortSignal };
|
||||
expect(init.signal.aborted).toBe(true);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it('takes a method error for a failed lookup, not for "nothing new" (#1041)', async () => {
|
||||
(secureFetch as ReturnType<typeof vi.fn>).mockImplementation(async (url: string) => {
|
||||
if (url.endsWith('/.well-known/jmap')) {
|
||||
|
||||
@@ -321,13 +321,42 @@ function rewriteApiUrl(session: JMAPSession, serverUrl: string): string {
|
||||
return serverOrigin + session.apiUrl.slice(apiOrigin.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* How long one request of the push task may go unanswered. React Native's
|
||||
* HTTP client has no timeout of its own, so a request on a dead connection
|
||||
* waited until the task's 30 s were up and ended without any notification
|
||||
* (#94). This leaves time for the session and the lookup, and the failure
|
||||
* still shows the generic one.
|
||||
*/
|
||||
export const PUSH_REQUEST_TIMEOUT_MS = 10_000;
|
||||
|
||||
/** secureFetch that gives up after PUSH_REQUEST_TIMEOUT_MS. */
|
||||
async function fetchInTime(url: string, init: RequestInit): Promise<Response> {
|
||||
const controller = new AbortController();
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
const deadline = new Promise<never>((_, reject) => {
|
||||
timer = setTimeout(() => {
|
||||
controller.abort();
|
||||
reject(new PushLookupError(`No answer within ${PUSH_REQUEST_TIMEOUT_MS} ms`));
|
||||
}, PUSH_REQUEST_TIMEOUT_MS);
|
||||
});
|
||||
try {
|
||||
return await Promise.race([
|
||||
secureFetch(url, { ...init, signal: controller.signal, timeoutMs: PUSH_REQUEST_TIMEOUT_MS }),
|
||||
deadline,
|
||||
]);
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
async function openDetachedSession(accountId: string): Promise<DetachedSession | null> {
|
||||
let creds = await jmapClient.getStoredCredentials(accountId);
|
||||
if (!creds) return null;
|
||||
creds = await freshStoredCredentials(accountId, creds);
|
||||
const baseUrl = creds.serverUrl.replace(/\/+$/, '');
|
||||
const authHeader = authHeaderFor(creds);
|
||||
const res = await secureFetch(`${baseUrl}/.well-known/jmap`, {
|
||||
const res = await fetchInTime(`${baseUrl}/.well-known/jmap`, {
|
||||
headers: { Authorization: authHeader, Accept: 'application/json' },
|
||||
});
|
||||
if (!res.ok) throw new Error(`Session discovery failed: ${res.status}`);
|
||||
@@ -370,7 +399,7 @@ async function jmapPost(
|
||||
session: DetachedSession,
|
||||
methodCalls: JMAPMethodCall[],
|
||||
): Promise<Array<[string, Record<string, any>, string]>> {
|
||||
const response = await secureFetch(session.apiUrl, {
|
||||
const response = await fetchInTime(session.apiUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Authorization: session.authHeader },
|
||||
body: JSON.stringify({ using: [CAPABILITIES.CORE, CAPABILITIES.MAIL], methodCalls }),
|
||||
|
||||
Reference in New Issue
Block a user