mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 16:14:07 +00:00
The flush keeps one `outbox.dropOrphans` caller, the mail branch's daily one with its 7 days, and drafts' own query says what's needed, so `addHeldFileSource` and its test are gone. `unsentCount` counts local drafts, and a draft a waiting send names counts once. The scope check in `recordBlob` and the permanent-status rule moved to `lib/store/mail/attachments.ts`, with tests.
108 lines
3.4 KiB
TypeScript
108 lines
3.4 KiB
TypeScript
/**
|
|
* What a finished send looks like when it waits. An outbox entry is a finished
|
|
* message: everything a send does up to the transport ran when the user clicked
|
|
* Send (the organisation's checks, the label, signatures, plugin send checks,
|
|
* S/MIME or PGP with the user present). What is kept is the arguments of the
|
|
* final send, or the finished raw message of a plugin's send. Sending it later
|
|
* can't skip any of that.
|
|
*/
|
|
|
|
/** The arguments of useEmailStore.sendEmail after the client (positional, as the client takes them). */
|
|
export interface EmailSendItem {
|
|
kind: 'email';
|
|
args: unknown[];
|
|
}
|
|
|
|
export interface RawSendItem {
|
|
kind: 'raw';
|
|
/** The finished message, kept as an upload until it is sent. */
|
|
blobId: string;
|
|
identityId: string;
|
|
delayedUntil?: string;
|
|
envelopeRecipients?: string[];
|
|
/** submitRaw: no copy in Sent. */
|
|
submitOnly?: boolean;
|
|
}
|
|
|
|
export type SendItem = EmailSendItem | RawSendItem;
|
|
|
|
export interface OutboxSummary {
|
|
subject: string;
|
|
to: string[];
|
|
}
|
|
|
|
/**
|
|
* - waiting: goes out by itself when the server answers;
|
|
* - sending: on its way (a tab took it);
|
|
* - failed: the server refused it; the error says why;
|
|
* - uncertain: the connection broke after it may have reached the server.
|
|
* Looked up when the server is back; stays this way only when the lookup
|
|
* can't tell, and then it's the user's to send again or delete;
|
|
* - held: the organisation's rules got stricter while it waited.
|
|
*/
|
|
export type OutboxStatus = 'waiting' | 'sending' | 'failed' | 'uncertain' | 'held';
|
|
|
|
/** Why an entry failed: one of ours (a code the UI translates) or the server's own words. */
|
|
export interface OutboxError {
|
|
code?: 'file_missing' | 'no_sent_mailbox' | 'no_drafts_mailbox';
|
|
text?: string;
|
|
}
|
|
|
|
/** What the organisation required when the message was finished. */
|
|
export interface PolicyFingerprint {
|
|
labelRequired: boolean;
|
|
hasLabel: boolean;
|
|
guards: string[];
|
|
}
|
|
|
|
export interface StoredOutboxEntry {
|
|
id: number;
|
|
seq: number;
|
|
createdAt: number;
|
|
summary: OutboxSummary;
|
|
status: OutboxStatus;
|
|
sendingSince?: number;
|
|
/** When the first try that may have reached the server began. */
|
|
firstAttemptAt?: number;
|
|
lastAttemptAt?: number;
|
|
error?: OutboxError;
|
|
item: SendItem;
|
|
fingerprint?: PolicyFingerprint;
|
|
messageId?: string;
|
|
copyId?: string;
|
|
}
|
|
|
|
/** Stand-in blob ids of files kept on this device start with this. */
|
|
export const LOCAL_BLOB_PREFIX = 'bulwark-local-';
|
|
|
|
export function isLocalBlobId(blobId: unknown): blobId is string {
|
|
return typeof blobId === 'string' && blobId.startsWith(LOCAL_BLOB_PREFIX);
|
|
}
|
|
|
|
/** The stand-in blob ids an outbox entry needs uploaded first. */
|
|
export function heldBlobIds(item: SendItem): string[] {
|
|
if (item.kind === 'raw') return isLocalBlobId(item.blobId) ? [item.blobId] : [];
|
|
const attachments = item.args[10];
|
|
if (!Array.isArray(attachments)) return [];
|
|
return attachments.map((a) => (a as { blobId?: unknown }).blobId).filter(isLocalBlobId);
|
|
}
|
|
|
|
export type ConflictReason = 'changed' | 'gone' | 'refused';
|
|
|
|
export interface ConflictSummary {
|
|
subject: string;
|
|
/** `draft`: a draft saved here met a version changed elsewhere. Both are kept. */
|
|
op: 'update' | 'destroy' | 'draft';
|
|
reason: ConflictReason;
|
|
/** The server's word when it refused. */
|
|
detail?: string;
|
|
}
|
|
|
|
export interface StoredConflict {
|
|
id: number;
|
|
jmap: string;
|
|
recordId: string;
|
|
createdAt: number;
|
|
summary: ConflictSummary;
|
|
}
|