Open locked accounts handed to you, beside your own mail
When the server hands a locked account to the reader (urn:inbuxa:jmap delegation), the account popover offers it. Only mail follows the switch; the reader's own settings, push and notifications stay theirs. A red bar, a red wordmark with a padlock and the tab title say which account is in view. Read delegates can't change anything, organize delegates can't delete, and writing needs send-as. A delegation taken away drops back to the reader's own mail. 14 new strings in all nine catalogs, unreviewed (inbuxa AL-7, AL-8).
This commit is contained in:
1 parent
ffe1a898f5
commit
8674b70f62
22 files changed
+679
-25
No files matched your search
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Locked accounts handed to the reader (inbuxa audit-hold-lock spec, AL-7).
|
||||
*
|
||||
* The server marks one in the account's `urn:inbuxa:jmap` capability, as
|
||||
* `delegation: {locked, access, sendAs, until}`. Only that mark makes an
|
||||
* account switchable: a server advertises every capability on any shared
|
||||
* account, so "it has mail" proves nothing about what was handed over.
|
||||
*/
|
||||
|
||||
import type { Id, JmapSession } from "@/jmap/types";
|
||||
|
||||
export type DelegationAccess = "read" | "organize" | "full";
|
||||
|
||||
export interface Delegation {
|
||||
locked: boolean;
|
||||
access: DelegationAccess;
|
||||
sendAs: boolean;
|
||||
/** UTC date the delegation ends, if it does. */
|
||||
until: string | null;
|
||||
}
|
||||
|
||||
export interface DelegatedAccount {
|
||||
id: Id;
|
||||
name: string;
|
||||
delegation: Delegation;
|
||||
}
|
||||
|
||||
const INBUXA = "urn:inbuxa:jmap";
|
||||
|
||||
type SessionLike = Pick<JmapSession, "accounts">;
|
||||
|
||||
export function delegationOf(session: SessionLike | null, accountId: Id | null): Delegation | null {
|
||||
if (!session || !accountId) return null;
|
||||
const account = session.accounts[accountId];
|
||||
if (!account || account.isPersonal) return null;
|
||||
const raw = (account.accountCapabilities?.[INBUXA] as { delegation?: Partial<Delegation> } | undefined)?.delegation;
|
||||
if (!raw || raw.locked !== true) return null;
|
||||
const access: DelegationAccess = raw.access === "organize" || raw.access === "full" ? raw.access : "read";
|
||||
return {
|
||||
locked: true,
|
||||
access,
|
||||
sendAs: raw.sendAs === true && access !== "read",
|
||||
until: typeof raw.until === "string" ? raw.until : null,
|
||||
};
|
||||
}
|
||||
|
||||
/** Every locked account handed to the reader, by name. */
|
||||
export function delegatedAccounts(session: SessionLike | null): DelegatedAccount[] {
|
||||
if (!session) return [];
|
||||
return Object.entries(session.accounts)
|
||||
.map(([id, account]) => {
|
||||
const delegation = delegationOf(session, id);
|
||||
return delegation ? { id, name: account.name, delegation } : null;
|
||||
})
|
||||
.filter((a): a is DelegatedAccount => a !== null)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
|
||||
/** Whether the reader may change anything in the account (AL-6). */
|
||||
export function mayWrite(delegation: Delegation | null): boolean {
|
||||
return !delegation || delegation.access !== "read";
|
||||
}
|
||||
|
||||
/** Whether the reader may delete in the account (AL-6). */
|
||||
export function mayDestroy(delegation: Delegation | null): boolean {
|
||||
return !delegation || delegation.access === "full";
|
||||
}
|
||||
Reference in new issue
Block a user