New mail in the other signed-in accounts: counts, a dot, and a notification
ci / node (pull_request) Skipped
ci / version (pull_request) Skipped
ci / docker-build (pull_request) Skipped
ci / publish (pull_request) Skipped
github/ci (branch) GitHub Actions
ci / github (pull_request) Successful in 2m24s
ci / announce (pull_request) Skipped
ci / node (pull_request) Skipped
ci / version (pull_request) Skipped
ci / docker-build (pull_request) Skipped
ci / publish (pull_request) Skipped
github/ci (branch) GitHub Actions
ci / github (pull_request) Successful in 2m24s
ci / announce (pull_request) Skipped
With more than one account signed in (#49), mail arriving in one that isn't in front went unseen until someone switched to it (multi-account spec, MA-8). - GET /api/auth/accounts/unread answers the Inbox unread count of each account not in front, asked through that account's own session (its OAuth token renewed first if due), kept a minute per account. - The web app asks every two minutes while another account is signed in. The account menu shows each one's count beside its name, and the avatar carries a dot when any of them has unread mail. - When a count rises while the app is open and desktop notifications are on, a notification names the account ("New mail for [email protected]"); clicking it switches to that account. An account seen for the first time doesn't notify: its mail was already there. Not in this change: notifications with the app closed, which need each added account's own Web Push subscription. New strings (3, English only in the other ten catalogs): "New mail for {name}", "Unread in the Inbox: {count}", "Account: new mail in another account". Tests: the server answers the other account's count and nothing when alone; the client keeps the counts, notifies only on a rise and only with notifications on. Checked in Chrome against the mock. typecheck, tests (web 1541, server 277) and build pass.
This commit is contained in:
1 parent
68c91a1ad4
commit
5f27923ce6
6 files changed
+234
-2
No files matched your search
@@ -363,6 +363,30 @@ function liveOthers(c: Context): { cookie: string; session: LiveSession }[] {
|
||||
return out;
|
||||
}
|
||||
|
||||
/** inbuxa MA-8: Inbox unread counts of accounts not in front, briefly kept. */
|
||||
const UNREAD_CACHE_MS = 60_000;
|
||||
const unreadCache = new Map<string, { unread: number | null; at: number }>();
|
||||
|
||||
/** The Inbox's unread count for one session's account, or null when it has none. */
|
||||
async function inboxUnread(session: LiveSession): Promise<number | null> {
|
||||
const upstream = await getUpstreamSession(session.id, session.authorization, upstreamFor(session.username));
|
||||
const accountId = upstream.primaryAccounts?.["urn:ietf:params:jmap:mail"];
|
||||
if (!accountId) return null;
|
||||
const res = await fetch(absoluteUpstream(upstream.apiUrl, upstream.baseUrl), {
|
||||
method: "POST",
|
||||
headers: { authorization: session.authorization, "content-type": "application/json", accept: "application/json" },
|
||||
body: JSON.stringify({
|
||||
using: ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
|
||||
methodCalls: [["Mailbox/get", { accountId, ids: null, properties: ["role", "unreadEmails"] }, "0"]],
|
||||
}),
|
||||
signal: AbortSignal.timeout(config.upstreamTimeout),
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
const body = (await res.json()) as { methodResponses?: [string, { list?: { role?: string | null; unreadEmails?: number }[] }, string][] };
|
||||
const inbox = body.methodResponses?.[0]?.[1]?.list?.find((m) => m.role === "inbox");
|
||||
return typeof inbox?.unreadEmails === "number" ? inbox.unreadEmails : null;
|
||||
}
|
||||
|
||||
/** Whether the account in front may have more beside it, or why not. */
|
||||
async function addRefusal(c: Context, front: LiveSession): Promise<string | null> {
|
||||
if (1 + liveOthers(c).length >= MAX_ACCOUNTS) return "add_full";
|
||||
@@ -756,6 +780,32 @@ export function createApp(basePath = config.basePath): Hono<Env> {
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* inbuxa MA-8: the Inbox unread count of each account not in front, asked
|
||||
* through that account's own session, so the menu can say where new mail
|
||||
* is. A minute's cache per account: the web app asks every few minutes, and
|
||||
* several tabs may ask at once.
|
||||
*/
|
||||
api.get("/auth/accounts/unread", requireSession, async (c) => {
|
||||
const answers = await Promise.all(
|
||||
liveOthers(c).map(async ({ cookie, session }) => {
|
||||
const cached = unreadCache.get(session.id);
|
||||
if (cached && Date.now() - cached.at < UNREAD_CACHE_MS) return { id: session.id, unread: cached.unread };
|
||||
try {
|
||||
let live: LiveSession | null = session;
|
||||
if (live.tokens && needsRefresh(live.tokens)) live = await refreshSession(cookie, live);
|
||||
if (!live) return { id: session.id, unread: null };
|
||||
const unread = await inboxUnread(live);
|
||||
unreadCache.set(session.id, { unread, at: Date.now() });
|
||||
return { id: session.id, unread };
|
||||
} catch {
|
||||
return { id: session.id, unread: null };
|
||||
}
|
||||
}),
|
||||
);
|
||||
return c.json({ accounts: answers });
|
||||
});
|
||||
|
||||
/* inbuxa MA-B: bring another signed-in account to the front. */
|
||||
api.post("/auth/accounts/:id/front", requireSession, async (c) => {
|
||||
const frontCookie = getCookie(c, config.cookieName)!;
|
||||
|
||||
Reference in new issue
Block a user