mox/webmail/api.ts
Mechiel Lukkien 6c0439cf7b
webmail: when moving a single message out of/to the inbox, ask if user wants to create a rule to automatically do that server-side for future deliveries
if the message has a list-id header, we assume this is a (mailing) list
message, and we require a dkim/spf-verified domain (we prefer the shortest that
is a suffix of the list-id value). the rule we would add will mark such
messages as from a mailing list, changing filtering rules on incoming messages
(not enforcing dmarc policies). messages will be matched on list-id header and
will only match if they have the same dkim/spf-verified domain.

if the message doesn't have a list-id header, we'll ask to match based on
"message from" address.

we don't ask the user in several cases:
- if the destination/source mailbox is a special-use mailbox (e.g.
  trash,archive,sent,junk; inbox isn't included)
- if the rule already exist (no point in adding it again).
- if the user said "no, not for this list-id/from-address" in the past.
- if the user said "no, not for messages moved to this mailbox" in the past.

we'll add the rule if the message was moved out of the inbox.
if the message was moved to the inbox, we check if there is a matching rule
that we can remove.

we now remember the "no" answers (for list-id, msg-from-addr and mailbox) in
the account database.

to implement the msgfrom rules, this adds support to rulesets for matching on
message "from" address. before, we could match on smtp from address (and other
fields). rulesets now also have a field for comments. webmail adds a note that
it created the rule, with the date.

manual editing of the rulesets is still in the webaccount page. this webmail
functionality is just a convenient way to add/remove common rules.
2024-04-21 17:14:08 +02:00

1454 lines
82 KiB
TypeScript

// NOTE: GENERATED by github.com/mjl-/sherpats, DO NOT MODIFY
namespace api {
// Request is a request to an SSE connection to send messages, either for a new
// view, to continue with an existing view, or to a cancel an ongoing request.
export interface Request {
ID: number
SSEID: number // SSE connection.
ViewID: number // To indicate a request is a continuation (more results) of the previous view. Echoed in events, client checks if it is getting results for the latest request.
Cancel: boolean // If set, this request and its view are canceled. A new view must be started.
Query: Query
Page: Page
}
// Query is a request for messages that match filters, in a given order.
export interface Query {
OrderAsc: boolean // Order by received ascending or desending.
Threading: ThreadMode
Filter: Filter
NotFilter: NotFilter
}
// Filter selects the messages to return. Fields that are set must all match,
// for slices each element by match ("and").
export interface Filter {
MailboxID: number // If -1, then all mailboxes except Trash/Junk/Rejects. Otherwise, only active if > 0.
MailboxChildrenIncluded: boolean // If true, also submailboxes are included in the search.
MailboxName: string // In case client doesn't know mailboxes and their IDs yet. Only used during sse connection setup, where it is turned into a MailboxID. Filtering only looks at MailboxID.
Words?: string[] | null // Case insensitive substring match for each string.
From?: string[] | null
To?: string[] | null // Including Cc and Bcc.
Oldest?: Date | null
Newest?: Date | null
Subject?: string[] | null
Attachments: AttachmentType
Labels?: string[] | null
Headers?: (string[] | null)[] | null // Header values can be empty, it's a check if the header is present, regardless of value.
SizeMin: number
SizeMax: number
}
// NotFilter matches messages that don't match these fields.
export interface NotFilter {
Words?: string[] | null
From?: string[] | null
To?: string[] | null
Subject?: string[] | null
Attachments: AttachmentType
Labels?: string[] | null
}
// Page holds pagination parameters for a request.
export interface Page {
AnchorMessageID: number // Start returning messages after this ID, if > 0. For pagination, fetching the next set of messages.
Count: number // Number of messages to return, must be >= 1, we never return more than 10000 for one request.
DestMessageID: number // If > 0, return messages until DestMessageID is found. More than Count messages can be returned. For long-running searches, it may take a while before this message if found.
}
// ParsedMessage has more parsed/derived information about a message, intended
// for rendering the (contents of the) message. Information from MessageItem is
// not duplicated.
export interface ParsedMessage {
ID: number
Part: Part
Headers?: { [key: string]: string[] | null }
ViewMode: ViewMode
Texts?: string[] | null // Text parts, can be empty.
HasHTML: boolean // Whether there is an HTML part. The webclient renders HTML message parts through an iframe and a separate request with strict CSP headers to prevent script execution and loading of external resources, which isn't possible when loading in iframe with inline HTML because not all browsers support the iframe csp attribute.
ListReplyAddress?: MessageAddress | null // From List-Post.
}
// Part represents a whole mail message, or a part of a multipart message. It
// is designed to handle IMAP requirements efficiently.
export interface Part {
BoundaryOffset: number // Offset in message where bound starts. -1 for top-level message.
HeaderOffset: number // Offset in message file where header starts.
BodyOffset: number // Offset in message file where body starts.
EndOffset: number // Where body of part ends. Set when part is fully read.
RawLineCount: number // Number of lines in raw, undecoded, body of part. Set when part is fully read.
DecodedSize: number // Number of octets when decoded. If this is a text mediatype, lines ending only in LF are changed end in CRLF and DecodedSize reflects that.
MediaType: string // From Content-Type, upper case. E.g. "TEXT". Can be empty because content-type may be absent. In this case, the part may be treated as TEXT/PLAIN.
MediaSubType: string // From Content-Type, upper case. E.g. "PLAIN".
ContentTypeParams?: { [key: string]: string } // E.g. holds "boundary" for multipart messages. Has lower-case keys, and original case values.
ContentID: string
ContentDescription: string
ContentTransferEncoding: string // In upper case.
Envelope?: Envelope | null // Email message headers. Not for non-message parts.
Parts?: Part[] | null // Parts if this is a multipart.
Message?: Part | null // Only for message/rfc822 and message/global. This part may have a buffer as backing io.ReaderAt, because a message/global can have a non-identity content-transfer-encoding. This part has a nil parent.
}
// Envelope holds the basic/common message headers as used in IMAP4.
export interface Envelope {
Date: Date
Subject: string // Q/B-word-decoded.
From?: Address[] | null
Sender?: Address[] | null
ReplyTo?: Address[] | null
To?: Address[] | null
CC?: Address[] | null
BCC?: Address[] | null
InReplyTo: string // From In-Reply-To header, includes <>.
MessageID: string // From Message-Id header, includes <>.
}
// Address as used in From and To headers.
export interface Address {
Name: string // Free-form name for display in mail applications.
User: string // Localpart, encoded as string. Must be parsed before using as Localpart.
Host: string // Domain in ASCII.
}
// MessageAddress is like message.Address, but with a dns.Domain, with unicode name
// included.
export interface MessageAddress {
Name: string // Free-form name for display in mail applications.
User: string // Localpart, encoded.
Domain: Domain
}
// Domain is a domain name, with one or more labels, with at least an ASCII
// representation, and for IDNA non-ASCII domains a unicode representation.
// The ASCII string must be used for DNS lookups. The strings do not have a
// trailing dot. When using with StrictResolver, add the trailing dot.
export interface Domain {
ASCII: string // A non-unicode domain, e.g. with A-labels (xn--...) or NR-LDH (non-reserved letters/digits/hyphens) labels. Always in lower case. No trailing dot.
Unicode: string // Name as U-labels, in Unicode NFC. Empty if this is an ASCII-only domain. No trailing dot.
}
// FromAddressSettings are webmail client settings per "From" address.
export interface FromAddressSettings {
FromAddress: string // Unicode.
ViewMode: ViewMode
}
// ComposeMessage is a message to be composed, for saving draft messages.
export interface ComposeMessage {
From: string
To?: string[] | null
Cc?: string[] | null
Bcc?: string[] | null
ReplyTo: string // If non-empty, Reply-To header to add to message.
Subject: string
TextBody: string
ResponseMessageID: number // If set, this was a reply or forward, based on IsForward.
DraftMessageID: number // If set, previous draft message that will be removed after composing new message.
}
// SubmitMessage is an email message to be sent to one or more recipients.
// Addresses are formatted as just email address, or with a name like "name
// <user@host>".
export interface SubmitMessage {
From: string
To?: string[] | null
Cc?: string[] | null
Bcc?: string[] | null
ReplyTo: string // If non-empty, Reply-To header to add to message.
Subject: string
TextBody: string
Attachments?: File[] | null
ForwardAttachments: ForwardAttachments
IsForward: boolean
ResponseMessageID: number // If set, this was a reply or forward, based on IsForward.
UserAgent: string // User-Agent header added if not empty.
RequireTLS?: boolean | null // For "Require TLS" extension during delivery.
FutureRelease?: Date | null // If set, time (in the future) when message should be delivered from queue.
ArchiveThread: boolean // If set, thread is archived after sending message.
DraftMessageID: number // If set, draft message that will be removed after sending.
}
// File is a new attachment (not from an existing message that is being
// forwarded) to send with a SubmitMessage.
export interface File {
Filename: string
DataURI: string // Full data of the attachment, with base64 encoding and including content-type.
}
// ForwardAttachments references attachments by a list of message.Part paths.
export interface ForwardAttachments {
MessageID: number // Only relevant if MessageID is not 0.
Paths?: (number[] | null)[] | null // List of attachments, each path is a list of indices into the top-level message.Part.Parts.
}
// Mailbox is collection of messages, e.g. Inbox or Sent.
export interface Mailbox {
ID: number
Name: string // "Inbox" is the name for the special IMAP "INBOX". Slash separated for hierarchy.
UIDValidity: number // If UIDs are invalidated, e.g. when renaming a mailbox to a previously existing name, UIDValidity must be changed. Used by IMAP for synchronization.
UIDNext: UID // UID likely to be assigned to next message. Used by IMAP to detect messages delivered to a mailbox.
Archive: boolean
Draft: boolean
Junk: boolean
Sent: boolean
Trash: boolean
Keywords?: string[] | null // Keywords as used in messages. Storing a non-system keyword for a message automatically adds it to this list. Used in the IMAP FLAGS response. Only "atoms" are allowed (IMAP syntax), keywords are case-insensitive, only stored in lower case (for JMAP), sorted.
HaveCounts: boolean // Whether MailboxCounts have been initialized.
Total: number // Total number of messages, excluding \Deleted. For JMAP.
Deleted: number // Number of messages with \Deleted flag. Used for IMAP message count that includes messages with \Deleted.
Unread: number // Messages without \Seen, excluding those with \Deleted, for JMAP.
Unseen: number // Messages without \Seen, including those with \Deleted, for IMAP.
Size: number // Number of bytes for all messages.
}
// RecipientSecurity is a quick analysis of the security properties of delivery to
// the recipient (domain).
export interface RecipientSecurity {
STARTTLS: SecurityResult // Whether recipient domain supports (opportunistic) STARTTLS, as seen during most recent delivery attempt. Will be "unknown" if no delivery to the domain has been attempted yet.
MTASTS: SecurityResult // Whether we have a stored enforced MTA-STS policy, or domain has MTA-STS DNS record.
DNSSEC: SecurityResult // Whether MX lookup response was DNSSEC-signed.
DANE: SecurityResult // Whether first delivery destination has DANE records.
RequireTLS: SecurityResult // Whether recipient domain is known to implement the REQUIRETLS SMTP extension. Will be "unknown" if no delivery to the domain has been attempted yet.
}
// Settings are webmail client settings.
export interface Settings {
ID: number // Singleton ID 1.
Signature: string
Quoting: Quoting
ShowAddressSecurity: boolean // Whether to show the bars underneath the address input fields indicating starttls/dnssec/dane/mtasts/requiretls support by address.
}
export interface Ruleset {
SMTPMailFromRegexp: string
MsgFromRegexp: string
VerifiedDomain: string
HeadersRegexp?: { [key: string]: string }
IsForward: boolean // todo: once we implement ARC, we can use dkim domains that we cannot verify but that the arc-verified forwarding mail server was able to verify.
ListAllowDomain: string
AcceptRejectsToMailbox: string
Mailbox: string
Comment: string
VerifiedDNSDomain: Domain
ListAllowDNSDomain: Domain
}
// EventStart is the first message sent on an SSE connection, giving the client
// basic data to populate its UI. After this event, messages will follow quickly in
// an EventViewMsgs event.
export interface EventStart {
SSEID: number
LoginAddress: MessageAddress
Addresses?: MessageAddress[] | null
DomainAddressConfigs?: { [key: string]: DomainAddressConfig } // ASCII domain to address config.
MailboxName: string
Mailboxes?: Mailbox[] | null
RejectsMailbox: string
Settings: Settings
AccountPath: string // If nonempty, the path on same host to webaccount interface.
Version: string
}
// DomainAddressConfig has the address (localpart) configuration for a domain, so
// the webmail client can decide if an address matches the addresses of the
// account.
export interface DomainAddressConfig {
LocalpartCatchallSeparator: string // Can be empty.
LocalpartCaseSensitive: boolean
}
// EventViewErr indicates an error during a query for messages. The request is
// aborted, no more request-related messages will be sent until the next request.
export interface EventViewErr {
ViewID: number
RequestID: number
Err: string // To be displayed in client.
}
// EventViewReset indicates that a request for the next set of messages in a few
// could not be fulfilled, e.g. because the anchor message does not exist anymore.
// The client should clear its list of messages. This can happen before
// EventViewMsgs events are sent.
export interface EventViewReset {
ViewID: number
RequestID: number
}
// EventViewMsgs contains messages for a view, possibly a continuation of an
// earlier list of messages.
export interface EventViewMsgs {
ViewID: number
RequestID: number
MessageItems?: (MessageItem[] | null)[] | null // If empty, this was the last message for the request. If non-empty, a list of thread messages. Each with the first message being the reason this thread is included and can be used as AnchorID in followup requests. If the threading mode is "off" in the query, there will always be only a single message. If a thread is sent, all messages in the thread are sent, including those that don't match the query (e.g. from another mailbox). Threads can be displayed based on the ThreadParentIDs field, with possibly slightly different display based on field ThreadMissingLink.
ParsedMessage?: ParsedMessage | null // If set, will match the target page.DestMessageID from the request.
ViewEnd: boolean // If set, there are no more messages in this view at this moment. Messages can be added, typically via Change messages, e.g. for new deliveries.
}
// MessageItem is sent by queries, it has derived information analyzed from
// message.Part, made for the needs of the message items in the message list.
// messages.
export interface MessageItem {
Message: Message // Without ParsedBuf and MsgPrefix, for size.
Envelope: MessageEnvelope
Attachments?: Attachment[] | null
IsSigned: boolean
IsEncrypted: boolean
FirstLine: string // Of message body, for showing as preview.
MatchQuery: boolean // If message does not match query, it can still be included because of threading.
}
// Message stored in database and per-message file on disk.
//
// Contents are always the combined data from MsgPrefix and the on-disk file named
// based on ID.
//
// Messages always have a header section, even if empty. Incoming messages without
// header section must get an empty header section added before inserting.
export interface Message {
ID: number // ID, unchanged over lifetime, determines path to on-disk msg file. Set during deliver.
UID: UID // UID, for IMAP. Set during deliver.
MailboxID: number
ModSeq: ModSeq // Modification sequence, for faster syncing with IMAP QRESYNC and JMAP. ModSeq is the last modification. CreateSeq is the Seq the message was inserted, always <= ModSeq. If Expunged is set, the message has been removed and should not be returned to the user. In this case, ModSeq is the Seq where the message is removed, and will never be changed again. We have an index on both ModSeq (for JMAP that synchronizes per account) and MailboxID+ModSeq (for IMAP that synchronizes per mailbox). The index on CreateSeq helps efficiently finding created messages for JMAP. The value of ModSeq is special for IMAP. Messages that existed before ModSeq was added have 0 as value. But modseq 0 in IMAP is special, so we return it as 1. If we get modseq 1 from a client, the IMAP server will translate it to 0. When we return modseq to clients, we turn 0 into 1.
CreateSeq: ModSeq
Expunged: boolean
IsReject: boolean // If set, this message was delivered to a Rejects mailbox. When it is moved to a different mailbox, its MailboxOrigID is set to the destination mailbox and this flag cleared.
IsForward: boolean // If set, this is a forwarded message (through a ruleset with IsForward). This causes fields used during junk analysis to be moved to their Orig variants, and masked IP fields cleared, so they aren't used in junk classifications for incoming messages. This ensures the forwarded messages don't cause negative reputation for the forwarding mail server, which may also be sending regular messages.
MailboxOrigID: number // MailboxOrigID is the mailbox the message was originally delivered to. Typically Inbox or Rejects, but can also be a mailbox configured in a Ruleset, or Postmaster, TLS/DMARC reporting addresses. MailboxOrigID is not changed when the message is moved to another mailbox, e.g. Archive/Trash/Junk. Used for per-mailbox reputation. MailboxDestinedID is normally 0, but when a message is delivered to the Rejects mailbox, it is set to the intended mailbox according to delivery rules, typically that of Inbox. When such a message is moved out of Rejects, the MailboxOrigID is corrected by setting it to MailboxDestinedID. This ensures the message is used for reputation calculation for future deliveries to that mailbox. These are not bstore references to prevent having to update all messages in a mailbox when the original mailbox is removed. Use of these fields requires checking if the mailbox still exists.
MailboxDestinedID: number
Received: Date
RemoteIP: string // Full IP address of remote SMTP server. Empty if not delivered over SMTP. The masked IPs are used to classify incoming messages. They are left empty for messages matching a ruleset for forwarded messages.
RemoteIPMasked1: string // For IPv4 /32, for IPv6 /64, for reputation.
RemoteIPMasked2: string // For IPv4 /26, for IPv6 /48.
RemoteIPMasked3: string // For IPv4 /21, for IPv6 /32.
EHLODomain: string // Only set if present and not an IP address. Unicode string. Empty for forwarded messages.
MailFrom: string // With localpart and domain. Can be empty.
MailFromLocalpart: Localpart // SMTP "MAIL FROM", can be empty.
MailFromDomain: string // Only set if it is a domain, not an IP. Unicode string. Empty for forwarded messages, but see OrigMailFromDomain.
RcptToLocalpart: Localpart // SMTP "RCPT TO", can be empty.
RcptToDomain: string // Unicode string.
MsgFromLocalpart: Localpart // Parsed "From" message header, used for reputation along with domain validation.
MsgFromDomain: string // Unicode string.
MsgFromOrgDomain: string // Unicode string.
EHLOValidated: boolean // Simplified statements of the Validation fields below, used for incoming messages to check reputation.
MailFromValidated: boolean
MsgFromValidated: boolean
EHLOValidation: Validation // Validation can also take reverse IP lookup into account, not only SPF.
MailFromValidation: Validation // Can have SPF-specific validations like ValidationSoftfail.
MsgFromValidation: Validation // Desirable validations: Strict, DMARC, Relaxed. Will not be just Pass.
DKIMDomains?: string[] | null // Domains with verified DKIM signatures. Unicode string. For forwarded messages, a DKIM domain that matched a ruleset's verified domain is left out, but included in OrigDKIMDomains.
OrigEHLODomain: string // For forwarded messages,
OrigDKIMDomains?: string[] | null
MessageID: string // Canonicalized Message-Id, always lower-case and normalized quoting, without <>'s. Empty if missing. Used for matching message threads, and to prevent duplicate reject delivery.
SubjectBase: string // For matching threads in case there is no References/In-Reply-To header. It is lower-cased, white-space collapsed, mailing list tags and re/fwd tags removed.
MessageHash?: string | null // Hash of message. For rejects delivery in case there is no Message-ID, only set when delivered as reject.
ThreadID: number // ID of message starting this thread.
ThreadParentIDs?: number[] | null // IDs of parent messages, from closest parent to the root message. Parent messages may be in a different mailbox, or may no longer exist. ThreadParentIDs must never contain the message id itself (a cycle), and parent messages must reference the same ancestors.
ThreadMissingLink: boolean // ThreadMissingLink is true if there is no match with a direct parent. E.g. first ID in ThreadParentIDs is not the direct ancestor (an intermediate message may have been deleted), or subject-based matching was done.
ThreadMuted: boolean // If set, newly delivered child messages are automatically marked as read. This field is copied to new child messages. Changes are propagated to the webmail client.
ThreadCollapsed: boolean // If set, this (sub)thread is collapsed in the webmail client, for threading mode "on" (mode "unread" ignores it). This field is copied to new child message. Changes are propagated to the webmail client.
IsMailingList: boolean // If received message was known to match a mailing list rule (with modified junk filtering).
DSN: boolean // If this message is a DSN, generated by us or received. For DSNs, we don't look at the subject when matching threads.
ReceivedTLSVersion: number // 0 if unknown, 1 if plaintext/no TLS, otherwise TLS cipher suite.
ReceivedTLSCipherSuite: number
ReceivedRequireTLS: boolean // Whether RequireTLS was known to be used for incoming delivery.
Seen: boolean
Answered: boolean
Flagged: boolean
Forwarded: boolean
Junk: boolean
Notjunk: boolean
Deleted: boolean
Draft: boolean
Phishing: boolean
MDNSent: boolean
Keywords?: string[] | null // For keywords other than system flags or the basic well-known $-flags. Only in "atom" syntax (IMAP), they are case-insensitive, always stored in lower-case (for JMAP), sorted.
Size: number
TrainedJunk?: boolean | null // If nil, no training done yet. Otherwise, true is trained as junk, false trained as nonjunk.
MsgPrefix?: string | null // Typically holds received headers and/or header separator.
ParsedBuf?: string | null // ParsedBuf message structure. Currently saved as JSON of message.Part because bstore cannot yet store recursive types. Created when first needed, and saved in the database. todo: once replaced with non-json storage, remove date fixup in ../message/part.go.
}
// MessageEnvelope is like message.Envelope, as used in message.Part, but including
// unicode host names for IDNA names.
export interface MessageEnvelope {
Date: Date // todo: should get sherpadoc to understand type embeds and embed the non-MessageAddress fields from message.Envelope.
Subject: string
From?: MessageAddress[] | null
Sender?: MessageAddress[] | null
ReplyTo?: MessageAddress[] | null
To?: MessageAddress[] | null
CC?: MessageAddress[] | null
BCC?: MessageAddress[] | null
InReplyTo: string
MessageID: string
}
// Attachment is a MIME part is an existing message that is not intended as
// viewable text or HTML part.
export interface Attachment {
Path?: number[] | null // Indices into top-level message.Part.Parts.
Filename: string // File name based on "name" attribute of "Content-Type", or the "filename" attribute of "Content-Disposition".
Part: Part
}
// EventViewChanges contain one or more changes relevant for the client, either
// with new mailbox total/unseen message counts, or messages added/removed/modified
// (flags) for the current view.
export interface EventViewChanges {
ViewID: number
Changes?: (any[] | null)[] | null // The first field of [2]any is a string, the second of the Change types below.
}
// ChangeMsgAdd adds a new message and possibly its thread to the view.
export interface ChangeMsgAdd {
MailboxID: number
UID: UID
ModSeq: ModSeq
Flags: Flags // System flags.
Keywords?: string[] | null // Other flags.
MessageItems?: MessageItem[] | null
}
// Flags for a mail message.
export interface Flags {
Seen: boolean
Answered: boolean
Flagged: boolean
Forwarded: boolean
Junk: boolean
Notjunk: boolean
Deleted: boolean
Draft: boolean
Phishing: boolean
MDNSent: boolean
}
// ChangeMsgRemove removes one or more messages from the view.
export interface ChangeMsgRemove {
MailboxID: number
UIDs?: UID[] | null // Must be in increasing UID order, for IMAP.
ModSeq: ModSeq
}
// ChangeMsgFlags updates flags for one message.
export interface ChangeMsgFlags {
MailboxID: number
UID: UID
ModSeq: ModSeq
Mask: Flags // Which flags are actually modified.
Flags: Flags // New flag values. All are set, not just mask.
Keywords?: string[] | null // Non-system/well-known flags/keywords/labels.
}
// ChangeMsgThread updates muted/collapsed fields for one message.
export interface ChangeMsgThread {
MessageIDs?: number[] | null
Muted: boolean
Collapsed: boolean
}
// ChangeMailboxRemove indicates a mailbox was removed, including all its messages.
export interface ChangeMailboxRemove {
MailboxID: number
Name: string
}
// ChangeMailboxAdd indicates a new mailbox was added, initially without any messages.
export interface ChangeMailboxAdd {
Mailbox: Mailbox
}
// ChangeMailboxRename indicates a mailbox was renamed. Its ID stays the same.
// It could be under a new parent.
export interface ChangeMailboxRename {
MailboxID: number
OldName: string
NewName: string
Flags?: string[] | null
}
// ChangeMailboxCounts set new total and unseen message counts for a mailbox.
export interface ChangeMailboxCounts {
MailboxID: number
MailboxName: string
Total: number // Total number of messages, excluding \Deleted. For JMAP.
Deleted: number // Number of messages with \Deleted flag. Used for IMAP message count that includes messages with \Deleted.
Unread: number // Messages without \Seen, excluding those with \Deleted, for JMAP.
Unseen: number // Messages without \Seen, including those with \Deleted, for IMAP.
Size: number // Number of bytes for all messages.
}
// ChangeMailboxSpecialUse has updated special-use flags for a mailbox.
export interface ChangeMailboxSpecialUse {
MailboxID: number
MailboxName: string
SpecialUse: SpecialUse
}
// SpecialUse identifies a specific role for a mailbox, used by clients to
// understand where messages should go.
export interface SpecialUse {
Archive: boolean
Draft: boolean
Junk: boolean
Sent: boolean
Trash: boolean
}
// ChangeMailboxKeywords has an updated list of keywords for a mailbox, e.g. after
// a message was added with a keyword that wasn't in the mailbox yet.
export interface ChangeMailboxKeywords {
MailboxID: number
MailboxName: string
Keywords?: string[] | null
}
// IMAP UID.
export type UID = number
// ModSeq represents a modseq as stored in the database. ModSeq 0 in the
// database is sent to the client as 1, because modseq 0 is special in IMAP.
// ModSeq coming from the client are of type int64.
export type ModSeq = number
// Validation of "message From" domain.
export enum Validation {
ValidationUnknown = 0,
ValidationStrict = 1, // Like DMARC, with strict policies.
ValidationDMARC = 2, // Actual DMARC policy.
ValidationRelaxed = 3, // Like DMARC, with relaxed policies.
ValidationPass = 4, // For SPF.
ValidationNeutral = 5, // For SPF.
ValidationTemperror = 6,
ValidationPermerror = 7,
ValidationFail = 8,
ValidationSoftfail = 9, // For SPF.
ValidationNone = 10, // E.g. No records.
}
export type CSRFToken = string
export enum ThreadMode {
ThreadOff = "off",
ThreadOn = "on",
ThreadUnread = "unread",
}
// AttachmentType is for filtering by attachment type.
export enum AttachmentType {
AttachmentIndifferent = "",
AttachmentNone = "none",
AttachmentAny = "any",
AttachmentImage = "image", // png, jpg, gif, ...
AttachmentPDF = "pdf",
AttachmentArchive = "archive", // zip files, tgz, ...
AttachmentSpreadsheet = "spreadsheet", // ods, xlsx, ...
AttachmentDocument = "document", // odt, docx, ...
AttachmentPresentation = "presentation", // odp, pptx, ...
}
// ViewMode how a message should be viewed: its text parts, html parts, or html
// with loading external resources.
export enum ViewMode {
ModeDefault = "",
ModeText = "text",
ModeHTML = "html",
ModeHTMLExt = "htmlext", // HTML with external resources.
}
// SecurityResult indicates whether a security feature is supported.
export enum SecurityResult {
SecurityResultError = "error",
SecurityResultNo = "no",
SecurityResultYes = "yes",
// Unknown whether supported. Finding out may only be (reasonably) possible when
// trying (e.g. SMTP STARTTLS). Once tried, the result may be cached for future
// lookups.
SecurityResultUnknown = "unknown",
}
// Quoting is a setting for how to quote in replies/forwards.
export enum Quoting {
Default = "", // Bottom-quote if text is selected, top-quote otherwise.
Bottom = "bottom",
Top = "top",
}
// Localpart is a decoded local part of an email address, before the "@".
// For quoted strings, values do not hold the double quote or escaping backslashes.
// An empty string can be a valid localpart.
// Localparts are in Unicode NFC.
export type Localpart = string
export const structTypes: {[typename: string]: boolean} = {"Address":true,"Attachment":true,"ChangeMailboxAdd":true,"ChangeMailboxCounts":true,"ChangeMailboxKeywords":true,"ChangeMailboxRemove":true,"ChangeMailboxRename":true,"ChangeMailboxSpecialUse":true,"ChangeMsgAdd":true,"ChangeMsgFlags":true,"ChangeMsgRemove":true,"ChangeMsgThread":true,"ComposeMessage":true,"Domain":true,"DomainAddressConfig":true,"Envelope":true,"EventStart":true,"EventViewChanges":true,"EventViewErr":true,"EventViewMsgs":true,"EventViewReset":true,"File":true,"Filter":true,"Flags":true,"ForwardAttachments":true,"FromAddressSettings":true,"Mailbox":true,"Message":true,"MessageAddress":true,"MessageEnvelope":true,"MessageItem":true,"NotFilter":true,"Page":true,"ParsedMessage":true,"Part":true,"Query":true,"RecipientSecurity":true,"Request":true,"Ruleset":true,"Settings":true,"SpecialUse":true,"SubmitMessage":true}
export const stringsTypes: {[typename: string]: boolean} = {"AttachmentType":true,"CSRFToken":true,"Localpart":true,"Quoting":true,"SecurityResult":true,"ThreadMode":true,"ViewMode":true}
export const intsTypes: {[typename: string]: boolean} = {"ModSeq":true,"UID":true,"Validation":true}
export const types: TypenameMap = {
"Request": {"Name":"Request","Docs":"","Fields":[{"Name":"ID","Docs":"","Typewords":["int64"]},{"Name":"SSEID","Docs":"","Typewords":["int64"]},{"Name":"ViewID","Docs":"","Typewords":["int64"]},{"Name":"Cancel","Docs":"","Typewords":["bool"]},{"Name":"Query","Docs":"","Typewords":["Query"]},{"Name":"Page","Docs":"","Typewords":["Page"]}]},
"Query": {"Name":"Query","Docs":"","Fields":[{"Name":"OrderAsc","Docs":"","Typewords":["bool"]},{"Name":"Threading","Docs":"","Typewords":["ThreadMode"]},{"Name":"Filter","Docs":"","Typewords":["Filter"]},{"Name":"NotFilter","Docs":"","Typewords":["NotFilter"]}]},
"Filter": {"Name":"Filter","Docs":"","Fields":[{"Name":"MailboxID","Docs":"","Typewords":["int64"]},{"Name":"MailboxChildrenIncluded","Docs":"","Typewords":["bool"]},{"Name":"MailboxName","Docs":"","Typewords":["string"]},{"Name":"Words","Docs":"","Typewords":["[]","string"]},{"Name":"From","Docs":"","Typewords":["[]","string"]},{"Name":"To","Docs":"","Typewords":["[]","string"]},{"Name":"Oldest","Docs":"","Typewords":["nullable","timestamp"]},{"Name":"Newest","Docs":"","Typewords":["nullable","timestamp"]},{"Name":"Subject","Docs":"","Typewords":["[]","string"]},{"Name":"Attachments","Docs":"","Typewords":["AttachmentType"]},{"Name":"Labels","Docs":"","Typewords":["[]","string"]},{"Name":"Headers","Docs":"","Typewords":["[]","[]","string"]},{"Name":"SizeMin","Docs":"","Typewords":["int64"]},{"Name":"SizeMax","Docs":"","Typewords":["int64"]}]},
"NotFilter": {"Name":"NotFilter","Docs":"","Fields":[{"Name":"Words","Docs":"","Typewords":["[]","string"]},{"Name":"From","Docs":"","Typewords":["[]","string"]},{"Name":"To","Docs":"","Typewords":["[]","string"]},{"Name":"Subject","Docs":"","Typewords":["[]","string"]},{"Name":"Attachments","Docs":"","Typewords":["AttachmentType"]},{"Name":"Labels","Docs":"","Typewords":["[]","string"]}]},
"Page": {"Name":"Page","Docs":"","Fields":[{"Name":"AnchorMessageID","Docs":"","Typewords":["int64"]},{"Name":"Count","Docs":"","Typewords":["int32"]},{"Name":"DestMessageID","Docs":"","Typewords":["int64"]}]},
"ParsedMessage": {"Name":"ParsedMessage","Docs":"","Fields":[{"Name":"ID","Docs":"","Typewords":["int64"]},{"Name":"Part","Docs":"","Typewords":["Part"]},{"Name":"Headers","Docs":"","Typewords":["{}","[]","string"]},{"Name":"ViewMode","Docs":"","Typewords":["ViewMode"]},{"Name":"Texts","Docs":"","Typewords":["[]","string"]},{"Name":"HasHTML","Docs":"","Typewords":["bool"]},{"Name":"ListReplyAddress","Docs":"","Typewords":["nullable","MessageAddress"]}]},
"Part": {"Name":"Part","Docs":"","Fields":[{"Name":"BoundaryOffset","Docs":"","Typewords":["int64"]},{"Name":"HeaderOffset","Docs":"","Typewords":["int64"]},{"Name":"BodyOffset","Docs":"","Typewords":["int64"]},{"Name":"EndOffset","Docs":"","Typewords":["int64"]},{"Name":"RawLineCount","Docs":"","Typewords":["int64"]},{"Name":"DecodedSize","Docs":"","Typewords":["int64"]},{"Name":"MediaType","Docs":"","Typewords":["string"]},{"Name":"MediaSubType","Docs":"","Typewords":["string"]},{"Name":"ContentTypeParams","Docs":"","Typewords":["{}","string"]},{"Name":"ContentID","Docs":"","Typewords":["string"]},{"Name":"ContentDescription","Docs":"","Typewords":["string"]},{"Name":"ContentTransferEncoding","Docs":"","Typewords":["string"]},{"Name":"Envelope","Docs":"","Typewords":["nullable","Envelope"]},{"Name":"Parts","Docs":"","Typewords":["[]","Part"]},{"Name":"Message","Docs":"","Typewords":["nullable","Part"]}]},
"Envelope": {"Name":"Envelope","Docs":"","Fields":[{"Name":"Date","Docs":"","Typewords":["timestamp"]},{"Name":"Subject","Docs":"","Typewords":["string"]},{"Name":"From","Docs":"","Typewords":["[]","Address"]},{"Name":"Sender","Docs":"","Typewords":["[]","Address"]},{"Name":"ReplyTo","Docs":"","Typewords":["[]","Address"]},{"Name":"To","Docs":"","Typewords":["[]","Address"]},{"Name":"CC","Docs":"","Typewords":["[]","Address"]},{"Name":"BCC","Docs":"","Typewords":["[]","Address"]},{"Name":"InReplyTo","Docs":"","Typewords":["string"]},{"Name":"MessageID","Docs":"","Typewords":["string"]}]},
"Address": {"Name":"Address","Docs":"","Fields":[{"Name":"Name","Docs":"","Typewords":["string"]},{"Name":"User","Docs":"","Typewords":["string"]},{"Name":"Host","Docs":"","Typewords":["string"]}]},
"MessageAddress": {"Name":"MessageAddress","Docs":"","Fields":[{"Name":"Name","Docs":"","Typewords":["string"]},{"Name":"User","Docs":"","Typewords":["string"]},{"Name":"Domain","Docs":"","Typewords":["Domain"]}]},
"Domain": {"Name":"Domain","Docs":"","Fields":[{"Name":"ASCII","Docs":"","Typewords":["string"]},{"Name":"Unicode","Docs":"","Typewords":["string"]}]},
"FromAddressSettings": {"Name":"FromAddressSettings","Docs":"","Fields":[{"Name":"FromAddress","Docs":"","Typewords":["string"]},{"Name":"ViewMode","Docs":"","Typewords":["ViewMode"]}]},
"ComposeMessage": {"Name":"ComposeMessage","Docs":"","Fields":[{"Name":"From","Docs":"","Typewords":["string"]},{"Name":"To","Docs":"","Typewords":["[]","string"]},{"Name":"Cc","Docs":"","Typewords":["[]","string"]},{"Name":"Bcc","Docs":"","Typewords":["[]","string"]},{"Name":"ReplyTo","Docs":"","Typewords":["string"]},{"Name":"Subject","Docs":"","Typewords":["string"]},{"Name":"TextBody","Docs":"","Typewords":["string"]},{"Name":"ResponseMessageID","Docs":"","Typewords":["int64"]},{"Name":"DraftMessageID","Docs":"","Typewords":["int64"]}]},
"SubmitMessage": {"Name":"SubmitMessage","Docs":"","Fields":[{"Name":"From","Docs":"","Typewords":["string"]},{"Name":"To","Docs":"","Typewords":["[]","string"]},{"Name":"Cc","Docs":"","Typewords":["[]","string"]},{"Name":"Bcc","Docs":"","Typewords":["[]","string"]},{"Name":"ReplyTo","Docs":"","Typewords":["string"]},{"Name":"Subject","Docs":"","Typewords":["string"]},{"Name":"TextBody","Docs":"","Typewords":["string"]},{"Name":"Attachments","Docs":"","Typewords":["[]","File"]},{"Name":"ForwardAttachments","Docs":"","Typewords":["ForwardAttachments"]},{"Name":"IsForward","Docs":"","Typewords":["bool"]},{"Name":"ResponseMessageID","Docs":"","Typewords":["int64"]},{"Name":"UserAgent","Docs":"","Typewords":["string"]},{"Name":"RequireTLS","Docs":"","Typewords":["nullable","bool"]},{"Name":"FutureRelease","Docs":"","Typewords":["nullable","timestamp"]},{"Name":"ArchiveThread","Docs":"","Typewords":["bool"]},{"Name":"DraftMessageID","Docs":"","Typewords":["int64"]}]},
"File": {"Name":"File","Docs":"","Fields":[{"Name":"Filename","Docs":"","Typewords":["string"]},{"Name":"DataURI","Docs":"","Typewords":["string"]}]},
"ForwardAttachments": {"Name":"ForwardAttachments","Docs":"","Fields":[{"Name":"MessageID","Docs":"","Typewords":["int64"]},{"Name":"Paths","Docs":"","Typewords":["[]","[]","int32"]}]},
"Mailbox": {"Name":"Mailbox","Docs":"","Fields":[{"Name":"ID","Docs":"","Typewords":["int64"]},{"Name":"Name","Docs":"","Typewords":["string"]},{"Name":"UIDValidity","Docs":"","Typewords":["uint32"]},{"Name":"UIDNext","Docs":"","Typewords":["UID"]},{"Name":"Archive","Docs":"","Typewords":["bool"]},{"Name":"Draft","Docs":"","Typewords":["bool"]},{"Name":"Junk","Docs":"","Typewords":["bool"]},{"Name":"Sent","Docs":"","Typewords":["bool"]},{"Name":"Trash","Docs":"","Typewords":["bool"]},{"Name":"Keywords","Docs":"","Typewords":["[]","string"]},{"Name":"HaveCounts","Docs":"","Typewords":["bool"]},{"Name":"Total","Docs":"","Typewords":["int64"]},{"Name":"Deleted","Docs":"","Typewords":["int64"]},{"Name":"Unread","Docs":"","Typewords":["int64"]},{"Name":"Unseen","Docs":"","Typewords":["int64"]},{"Name":"Size","Docs":"","Typewords":["int64"]}]},
"RecipientSecurity": {"Name":"RecipientSecurity","Docs":"","Fields":[{"Name":"STARTTLS","Docs":"","Typewords":["SecurityResult"]},{"Name":"MTASTS","Docs":"","Typewords":["SecurityResult"]},{"Name":"DNSSEC","Docs":"","Typewords":["SecurityResult"]},{"Name":"DANE","Docs":"","Typewords":["SecurityResult"]},{"Name":"RequireTLS","Docs":"","Typewords":["SecurityResult"]}]},
"Settings": {"Name":"Settings","Docs":"","Fields":[{"Name":"ID","Docs":"","Typewords":["uint8"]},{"Name":"Signature","Docs":"","Typewords":["string"]},{"Name":"Quoting","Docs":"","Typewords":["Quoting"]},{"Name":"ShowAddressSecurity","Docs":"","Typewords":["bool"]}]},
"Ruleset": {"Name":"Ruleset","Docs":"","Fields":[{"Name":"SMTPMailFromRegexp","Docs":"","Typewords":["string"]},{"Name":"MsgFromRegexp","Docs":"","Typewords":["string"]},{"Name":"VerifiedDomain","Docs":"","Typewords":["string"]},{"Name":"HeadersRegexp","Docs":"","Typewords":["{}","string"]},{"Name":"IsForward","Docs":"","Typewords":["bool"]},{"Name":"ListAllowDomain","Docs":"","Typewords":["string"]},{"Name":"AcceptRejectsToMailbox","Docs":"","Typewords":["string"]},{"Name":"Mailbox","Docs":"","Typewords":["string"]},{"Name":"Comment","Docs":"","Typewords":["string"]},{"Name":"VerifiedDNSDomain","Docs":"","Typewords":["Domain"]},{"Name":"ListAllowDNSDomain","Docs":"","Typewords":["Domain"]}]},
"EventStart": {"Name":"EventStart","Docs":"","Fields":[{"Name":"SSEID","Docs":"","Typewords":["int64"]},{"Name":"LoginAddress","Docs":"","Typewords":["MessageAddress"]},{"Name":"Addresses","Docs":"","Typewords":["[]","MessageAddress"]},{"Name":"DomainAddressConfigs","Docs":"","Typewords":["{}","DomainAddressConfig"]},{"Name":"MailboxName","Docs":"","Typewords":["string"]},{"Name":"Mailboxes","Docs":"","Typewords":["[]","Mailbox"]},{"Name":"RejectsMailbox","Docs":"","Typewords":["string"]},{"Name":"Settings","Docs":"","Typewords":["Settings"]},{"Name":"AccountPath","Docs":"","Typewords":["string"]},{"Name":"Version","Docs":"","Typewords":["string"]}]},
"DomainAddressConfig": {"Name":"DomainAddressConfig","Docs":"","Fields":[{"Name":"LocalpartCatchallSeparator","Docs":"","Typewords":["string"]},{"Name":"LocalpartCaseSensitive","Docs":"","Typewords":["bool"]}]},
"EventViewErr": {"Name":"EventViewErr","Docs":"","Fields":[{"Name":"ViewID","Docs":"","Typewords":["int64"]},{"Name":"RequestID","Docs":"","Typewords":["int64"]},{"Name":"Err","Docs":"","Typewords":["string"]}]},
"EventViewReset": {"Name":"EventViewReset","Docs":"","Fields":[{"Name":"ViewID","Docs":"","Typewords":["int64"]},{"Name":"RequestID","Docs":"","Typewords":["int64"]}]},
"EventViewMsgs": {"Name":"EventViewMsgs","Docs":"","Fields":[{"Name":"ViewID","Docs":"","Typewords":["int64"]},{"Name":"RequestID","Docs":"","Typewords":["int64"]},{"Name":"MessageItems","Docs":"","Typewords":["[]","[]","MessageItem"]},{"Name":"ParsedMessage","Docs":"","Typewords":["nullable","ParsedMessage"]},{"Name":"ViewEnd","Docs":"","Typewords":["bool"]}]},
"MessageItem": {"Name":"MessageItem","Docs":"","Fields":[{"Name":"Message","Docs":"","Typewords":["Message"]},{"Name":"Envelope","Docs":"","Typewords":["MessageEnvelope"]},{"Name":"Attachments","Docs":"","Typewords":["[]","Attachment"]},{"Name":"IsSigned","Docs":"","Typewords":["bool"]},{"Name":"IsEncrypted","Docs":"","Typewords":["bool"]},{"Name":"FirstLine","Docs":"","Typewords":["string"]},{"Name":"MatchQuery","Docs":"","Typewords":["bool"]}]},
"Message": {"Name":"Message","Docs":"","Fields":[{"Name":"ID","Docs":"","Typewords":["int64"]},{"Name":"UID","Docs":"","Typewords":["UID"]},{"Name":"MailboxID","Docs":"","Typewords":["int64"]},{"Name":"ModSeq","Docs":"","Typewords":["ModSeq"]},{"Name":"CreateSeq","Docs":"","Typewords":["ModSeq"]},{"Name":"Expunged","Docs":"","Typewords":["bool"]},{"Name":"IsReject","Docs":"","Typewords":["bool"]},{"Name":"IsForward","Docs":"","Typewords":["bool"]},{"Name":"MailboxOrigID","Docs":"","Typewords":["int64"]},{"Name":"MailboxDestinedID","Docs":"","Typewords":["int64"]},{"Name":"Received","Docs":"","Typewords":["timestamp"]},{"Name":"RemoteIP","Docs":"","Typewords":["string"]},{"Name":"RemoteIPMasked1","Docs":"","Typewords":["string"]},{"Name":"RemoteIPMasked2","Docs":"","Typewords":["string"]},{"Name":"RemoteIPMasked3","Docs":"","Typewords":["string"]},{"Name":"EHLODomain","Docs":"","Typewords":["string"]},{"Name":"MailFrom","Docs":"","Typewords":["string"]},{"Name":"MailFromLocalpart","Docs":"","Typewords":["Localpart"]},{"Name":"MailFromDomain","Docs":"","Typewords":["string"]},{"Name":"RcptToLocalpart","Docs":"","Typewords":["Localpart"]},{"Name":"RcptToDomain","Docs":"","Typewords":["string"]},{"Name":"MsgFromLocalpart","Docs":"","Typewords":["Localpart"]},{"Name":"MsgFromDomain","Docs":"","Typewords":["string"]},{"Name":"MsgFromOrgDomain","Docs":"","Typewords":["string"]},{"Name":"EHLOValidated","Docs":"","Typewords":["bool"]},{"Name":"MailFromValidated","Docs":"","Typewords":["bool"]},{"Name":"MsgFromValidated","Docs":"","Typewords":["bool"]},{"Name":"EHLOValidation","Docs":"","Typewords":["Validation"]},{"Name":"MailFromValidation","Docs":"","Typewords":["Validation"]},{"Name":"MsgFromValidation","Docs":"","Typewords":["Validation"]},{"Name":"DKIMDomains","Docs":"","Typewords":["[]","string"]},{"Name":"OrigEHLODomain","Docs":"","Typewords":["string"]},{"Name":"OrigDKIMDomains","Docs":"","Typewords":["[]","string"]},{"Name":"MessageID","Docs":"","Typewords":["string"]},{"Name":"SubjectBase","Docs":"","Typewords":["string"]},{"Name":"MessageHash","Docs":"","Typewords":["nullable","string"]},{"Name":"ThreadID","Docs":"","Typewords":["int64"]},{"Name":"ThreadParentIDs","Docs":"","Typewords":["[]","int64"]},{"Name":"ThreadMissingLink","Docs":"","Typewords":["bool"]},{"Name":"ThreadMuted","Docs":"","Typewords":["bool"]},{"Name":"ThreadCollapsed","Docs":"","Typewords":["bool"]},{"Name":"IsMailingList","Docs":"","Typewords":["bool"]},{"Name":"DSN","Docs":"","Typewords":["bool"]},{"Name":"ReceivedTLSVersion","Docs":"","Typewords":["uint16"]},{"Name":"ReceivedTLSCipherSuite","Docs":"","Typewords":["uint16"]},{"Name":"ReceivedRequireTLS","Docs":"","Typewords":["bool"]},{"Name":"Seen","Docs":"","Typewords":["bool"]},{"Name":"Answered","Docs":"","Typewords":["bool"]},{"Name":"Flagged","Docs":"","Typewords":["bool"]},{"Name":"Forwarded","Docs":"","Typewords":["bool"]},{"Name":"Junk","Docs":"","Typewords":["bool"]},{"Name":"Notjunk","Docs":"","Typewords":["bool"]},{"Name":"Deleted","Docs":"","Typewords":["bool"]},{"Name":"Draft","Docs":"","Typewords":["bool"]},{"Name":"Phishing","Docs":"","Typewords":["bool"]},{"Name":"MDNSent","Docs":"","Typewords":["bool"]},{"Name":"Keywords","Docs":"","Typewords":["[]","string"]},{"Name":"Size","Docs":"","Typewords":["int64"]},{"Name":"TrainedJunk","Docs":"","Typewords":["nullable","bool"]},{"Name":"MsgPrefix","Docs":"","Typewords":["nullable","string"]},{"Name":"ParsedBuf","Docs":"","Typewords":["nullable","string"]}]},
"MessageEnvelope": {"Name":"MessageEnvelope","Docs":"","Fields":[{"Name":"Date","Docs":"","Typewords":["timestamp"]},{"Name":"Subject","Docs":"","Typewords":["string"]},{"Name":"From","Docs":"","Typewords":["[]","MessageAddress"]},{"Name":"Sender","Docs":"","Typewords":["[]","MessageAddress"]},{"Name":"ReplyTo","Docs":"","Typewords":["[]","MessageAddress"]},{"Name":"To","Docs":"","Typewords":["[]","MessageAddress"]},{"Name":"CC","Docs":"","Typewords":["[]","MessageAddress"]},{"Name":"BCC","Docs":"","Typewords":["[]","MessageAddress"]},{"Name":"InReplyTo","Docs":"","Typewords":["string"]},{"Name":"MessageID","Docs":"","Typewords":["string"]}]},
"Attachment": {"Name":"Attachment","Docs":"","Fields":[{"Name":"Path","Docs":"","Typewords":["[]","int32"]},{"Name":"Filename","Docs":"","Typewords":["string"]},{"Name":"Part","Docs":"","Typewords":["Part"]}]},
"EventViewChanges": {"Name":"EventViewChanges","Docs":"","Fields":[{"Name":"ViewID","Docs":"","Typewords":["int64"]},{"Name":"Changes","Docs":"","Typewords":["[]","[]","any"]}]},
"ChangeMsgAdd": {"Name":"ChangeMsgAdd","Docs":"","Fields":[{"Name":"MailboxID","Docs":"","Typewords":["int64"]},{"Name":"UID","Docs":"","Typewords":["UID"]},{"Name":"ModSeq","Docs":"","Typewords":["ModSeq"]},{"Name":"Flags","Docs":"","Typewords":["Flags"]},{"Name":"Keywords","Docs":"","Typewords":["[]","string"]},{"Name":"MessageItems","Docs":"","Typewords":["[]","MessageItem"]}]},
"Flags": {"Name":"Flags","Docs":"","Fields":[{"Name":"Seen","Docs":"","Typewords":["bool"]},{"Name":"Answered","Docs":"","Typewords":["bool"]},{"Name":"Flagged","Docs":"","Typewords":["bool"]},{"Name":"Forwarded","Docs":"","Typewords":["bool"]},{"Name":"Junk","Docs":"","Typewords":["bool"]},{"Name":"Notjunk","Docs":"","Typewords":["bool"]},{"Name":"Deleted","Docs":"","Typewords":["bool"]},{"Name":"Draft","Docs":"","Typewords":["bool"]},{"Name":"Phishing","Docs":"","Typewords":["bool"]},{"Name":"MDNSent","Docs":"","Typewords":["bool"]}]},
"ChangeMsgRemove": {"Name":"ChangeMsgRemove","Docs":"","Fields":[{"Name":"MailboxID","Docs":"","Typewords":["int64"]},{"Name":"UIDs","Docs":"","Typewords":["[]","UID"]},{"Name":"ModSeq","Docs":"","Typewords":["ModSeq"]}]},
"ChangeMsgFlags": {"Name":"ChangeMsgFlags","Docs":"","Fields":[{"Name":"MailboxID","Docs":"","Typewords":["int64"]},{"Name":"UID","Docs":"","Typewords":["UID"]},{"Name":"ModSeq","Docs":"","Typewords":["ModSeq"]},{"Name":"Mask","Docs":"","Typewords":["Flags"]},{"Name":"Flags","Docs":"","Typewords":["Flags"]},{"Name":"Keywords","Docs":"","Typewords":["[]","string"]}]},
"ChangeMsgThread": {"Name":"ChangeMsgThread","Docs":"","Fields":[{"Name":"MessageIDs","Docs":"","Typewords":["[]","int64"]},{"Name":"Muted","Docs":"","Typewords":["bool"]},{"Name":"Collapsed","Docs":"","Typewords":["bool"]}]},
"ChangeMailboxRemove": {"Name":"ChangeMailboxRemove","Docs":"","Fields":[{"Name":"MailboxID","Docs":"","Typewords":["int64"]},{"Name":"Name","Docs":"","Typewords":["string"]}]},
"ChangeMailboxAdd": {"Name":"ChangeMailboxAdd","Docs":"","Fields":[{"Name":"Mailbox","Docs":"","Typewords":["Mailbox"]}]},
"ChangeMailboxRename": {"Name":"ChangeMailboxRename","Docs":"","Fields":[{"Name":"MailboxID","Docs":"","Typewords":["int64"]},{"Name":"OldName","Docs":"","Typewords":["string"]},{"Name":"NewName","Docs":"","Typewords":["string"]},{"Name":"Flags","Docs":"","Typewords":["[]","string"]}]},
"ChangeMailboxCounts": {"Name":"ChangeMailboxCounts","Docs":"","Fields":[{"Name":"MailboxID","Docs":"","Typewords":["int64"]},{"Name":"MailboxName","Docs":"","Typewords":["string"]},{"Name":"Total","Docs":"","Typewords":["int64"]},{"Name":"Deleted","Docs":"","Typewords":["int64"]},{"Name":"Unread","Docs":"","Typewords":["int64"]},{"Name":"Unseen","Docs":"","Typewords":["int64"]},{"Name":"Size","Docs":"","Typewords":["int64"]}]},
"ChangeMailboxSpecialUse": {"Name":"ChangeMailboxSpecialUse","Docs":"","Fields":[{"Name":"MailboxID","Docs":"","Typewords":["int64"]},{"Name":"MailboxName","Docs":"","Typewords":["string"]},{"Name":"SpecialUse","Docs":"","Typewords":["SpecialUse"]}]},
"SpecialUse": {"Name":"SpecialUse","Docs":"","Fields":[{"Name":"Archive","Docs":"","Typewords":["bool"]},{"Name":"Draft","Docs":"","Typewords":["bool"]},{"Name":"Junk","Docs":"","Typewords":["bool"]},{"Name":"Sent","Docs":"","Typewords":["bool"]},{"Name":"Trash","Docs":"","Typewords":["bool"]}]},
"ChangeMailboxKeywords": {"Name":"ChangeMailboxKeywords","Docs":"","Fields":[{"Name":"MailboxID","Docs":"","Typewords":["int64"]},{"Name":"MailboxName","Docs":"","Typewords":["string"]},{"Name":"Keywords","Docs":"","Typewords":["[]","string"]}]},
"UID": {"Name":"UID","Docs":"","Values":null},
"ModSeq": {"Name":"ModSeq","Docs":"","Values":null},
"Validation": {"Name":"Validation","Docs":"","Values":[{"Name":"ValidationUnknown","Value":0,"Docs":""},{"Name":"ValidationStrict","Value":1,"Docs":""},{"Name":"ValidationDMARC","Value":2,"Docs":""},{"Name":"ValidationRelaxed","Value":3,"Docs":""},{"Name":"ValidationPass","Value":4,"Docs":""},{"Name":"ValidationNeutral","Value":5,"Docs":""},{"Name":"ValidationTemperror","Value":6,"Docs":""},{"Name":"ValidationPermerror","Value":7,"Docs":""},{"Name":"ValidationFail","Value":8,"Docs":""},{"Name":"ValidationSoftfail","Value":9,"Docs":""},{"Name":"ValidationNone","Value":10,"Docs":""}]},
"CSRFToken": {"Name":"CSRFToken","Docs":"","Values":null},
"ThreadMode": {"Name":"ThreadMode","Docs":"","Values":[{"Name":"ThreadOff","Value":"off","Docs":""},{"Name":"ThreadOn","Value":"on","Docs":""},{"Name":"ThreadUnread","Value":"unread","Docs":""}]},
"AttachmentType": {"Name":"AttachmentType","Docs":"","Values":[{"Name":"AttachmentIndifferent","Value":"","Docs":""},{"Name":"AttachmentNone","Value":"none","Docs":""},{"Name":"AttachmentAny","Value":"any","Docs":""},{"Name":"AttachmentImage","Value":"image","Docs":""},{"Name":"AttachmentPDF","Value":"pdf","Docs":""},{"Name":"AttachmentArchive","Value":"archive","Docs":""},{"Name":"AttachmentSpreadsheet","Value":"spreadsheet","Docs":""},{"Name":"AttachmentDocument","Value":"document","Docs":""},{"Name":"AttachmentPresentation","Value":"presentation","Docs":""}]},
"ViewMode": {"Name":"ViewMode","Docs":"","Values":[{"Name":"ModeDefault","Value":"","Docs":""},{"Name":"ModeText","Value":"text","Docs":""},{"Name":"ModeHTML","Value":"html","Docs":""},{"Name":"ModeHTMLExt","Value":"htmlext","Docs":""}]},
"SecurityResult": {"Name":"SecurityResult","Docs":"","Values":[{"Name":"SecurityResultError","Value":"error","Docs":""},{"Name":"SecurityResultNo","Value":"no","Docs":""},{"Name":"SecurityResultYes","Value":"yes","Docs":""},{"Name":"SecurityResultUnknown","Value":"unknown","Docs":""}]},
"Quoting": {"Name":"Quoting","Docs":"","Values":[{"Name":"Default","Value":"","Docs":""},{"Name":"Bottom","Value":"bottom","Docs":""},{"Name":"Top","Value":"top","Docs":""}]},
"Localpart": {"Name":"Localpart","Docs":"","Values":null},
}
export const parser = {
Request: (v: any) => parse("Request", v) as Request,
Query: (v: any) => parse("Query", v) as Query,
Filter: (v: any) => parse("Filter", v) as Filter,
NotFilter: (v: any) => parse("NotFilter", v) as NotFilter,
Page: (v: any) => parse("Page", v) as Page,
ParsedMessage: (v: any) => parse("ParsedMessage", v) as ParsedMessage,
Part: (v: any) => parse("Part", v) as Part,
Envelope: (v: any) => parse("Envelope", v) as Envelope,
Address: (v: any) => parse("Address", v) as Address,
MessageAddress: (v: any) => parse("MessageAddress", v) as MessageAddress,
Domain: (v: any) => parse("Domain", v) as Domain,
FromAddressSettings: (v: any) => parse("FromAddressSettings", v) as FromAddressSettings,
ComposeMessage: (v: any) => parse("ComposeMessage", v) as ComposeMessage,
SubmitMessage: (v: any) => parse("SubmitMessage", v) as SubmitMessage,
File: (v: any) => parse("File", v) as File,
ForwardAttachments: (v: any) => parse("ForwardAttachments", v) as ForwardAttachments,
Mailbox: (v: any) => parse("Mailbox", v) as Mailbox,
RecipientSecurity: (v: any) => parse("RecipientSecurity", v) as RecipientSecurity,
Settings: (v: any) => parse("Settings", v) as Settings,
Ruleset: (v: any) => parse("Ruleset", v) as Ruleset,
EventStart: (v: any) => parse("EventStart", v) as EventStart,
DomainAddressConfig: (v: any) => parse("DomainAddressConfig", v) as DomainAddressConfig,
EventViewErr: (v: any) => parse("EventViewErr", v) as EventViewErr,
EventViewReset: (v: any) => parse("EventViewReset", v) as EventViewReset,
EventViewMsgs: (v: any) => parse("EventViewMsgs", v) as EventViewMsgs,
MessageItem: (v: any) => parse("MessageItem", v) as MessageItem,
Message: (v: any) => parse("Message", v) as Message,
MessageEnvelope: (v: any) => parse("MessageEnvelope", v) as MessageEnvelope,
Attachment: (v: any) => parse("Attachment", v) as Attachment,
EventViewChanges: (v: any) => parse("EventViewChanges", v) as EventViewChanges,
ChangeMsgAdd: (v: any) => parse("ChangeMsgAdd", v) as ChangeMsgAdd,
Flags: (v: any) => parse("Flags", v) as Flags,
ChangeMsgRemove: (v: any) => parse("ChangeMsgRemove", v) as ChangeMsgRemove,
ChangeMsgFlags: (v: any) => parse("ChangeMsgFlags", v) as ChangeMsgFlags,
ChangeMsgThread: (v: any) => parse("ChangeMsgThread", v) as ChangeMsgThread,
ChangeMailboxRemove: (v: any) => parse("ChangeMailboxRemove", v) as ChangeMailboxRemove,
ChangeMailboxAdd: (v: any) => parse("ChangeMailboxAdd", v) as ChangeMailboxAdd,
ChangeMailboxRename: (v: any) => parse("ChangeMailboxRename", v) as ChangeMailboxRename,
ChangeMailboxCounts: (v: any) => parse("ChangeMailboxCounts", v) as ChangeMailboxCounts,
ChangeMailboxSpecialUse: (v: any) => parse("ChangeMailboxSpecialUse", v) as ChangeMailboxSpecialUse,
SpecialUse: (v: any) => parse("SpecialUse", v) as SpecialUse,
ChangeMailboxKeywords: (v: any) => parse("ChangeMailboxKeywords", v) as ChangeMailboxKeywords,
UID: (v: any) => parse("UID", v) as UID,
ModSeq: (v: any) => parse("ModSeq", v) as ModSeq,
Validation: (v: any) => parse("Validation", v) as Validation,
CSRFToken: (v: any) => parse("CSRFToken", v) as CSRFToken,
ThreadMode: (v: any) => parse("ThreadMode", v) as ThreadMode,
AttachmentType: (v: any) => parse("AttachmentType", v) as AttachmentType,
ViewMode: (v: any) => parse("ViewMode", v) as ViewMode,
SecurityResult: (v: any) => parse("SecurityResult", v) as SecurityResult,
Quoting: (v: any) => parse("Quoting", v) as Quoting,
Localpart: (v: any) => parse("Localpart", v) as Localpart,
}
let defaultOptions: ClientOptions = {slicesNullable: true, mapsNullable: true, nullableOptional: true}
export class Client {
private baseURL: string
public authState: AuthState
public options: ClientOptions
constructor() {
this.authState = {}
this.options = {...defaultOptions}
this.baseURL = this.options.baseURL || defaultBaseURL
}
withAuthToken(token: string): Client {
const c = new Client()
c.authState.token = token
c.options = this.options
return c
}
withOptions(options: ClientOptions): Client {
const c = new Client()
c.authState = this.authState
c.options = { ...this.options, ...options }
return c
}
// LoginPrep returns a login token, and also sets it as cookie. Both must be
// present in the call to Login.
async LoginPrep(): Promise<string> {
const fn: string = "LoginPrep"
const paramTypes: string[][] = []
const returnTypes: string[][] = [["string"]]
const params: any[] = []
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as string
}
// Login returns a session token for the credentials, or fails with error code
// "user:badLogin". Call LoginPrep to get a loginToken.
async Login(loginToken: string, username: string, password: string): Promise<CSRFToken> {
const fn: string = "Login"
const paramTypes: string[][] = [["string"],["string"],["string"]]
const returnTypes: string[][] = [["CSRFToken"]]
const params: any[] = [loginToken, username, password]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as CSRFToken
}
// Logout invalidates the session token.
async Logout(): Promise<void> {
const fn: string = "Logout"
const paramTypes: string[][] = []
const returnTypes: string[][] = []
const params: any[] = []
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// Token returns a token to use for an SSE connection. A token can only be used for
// a single SSE connection. Tokens are stored in memory for a maximum of 1 minute,
// with at most 10 unused tokens (the most recently created) per account.
async Token(): Promise<string> {
const fn: string = "Token"
const paramTypes: string[][] = []
const returnTypes: string[][] = [["string"]]
const params: any[] = []
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as string
}
// Requests sends a new request for an open SSE connection. Any currently active
// request for the connection will be canceled, but this is done asynchrously, so
// the SSE connection may still send results for the previous request. Callers
// should take care to ignore such results. If req.Cancel is set, no new request is
// started.
async Request(req: Request): Promise<void> {
const fn: string = "Request"
const paramTypes: string[][] = [["Request"]]
const returnTypes: string[][] = []
const params: any[] = [req]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// ParsedMessage returns enough to render the textual body of a message. It is
// assumed the client already has other fields through MessageItem.
async ParsedMessage(msgID: number): Promise<ParsedMessage> {
const fn: string = "ParsedMessage"
const paramTypes: string[][] = [["int64"]]
const returnTypes: string[][] = [["ParsedMessage"]]
const params: any[] = [msgID]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as ParsedMessage
}
// FromAddressSettingsSave saves per-"From"-address settings.
async FromAddressSettingsSave(fas: FromAddressSettings): Promise<void> {
const fn: string = "FromAddressSettingsSave"
const paramTypes: string[][] = [["FromAddressSettings"]]
const returnTypes: string[][] = []
const params: any[] = [fas]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// MessageFindMessageID looks up a message by Message-Id header, and returns the ID
// of the message in storage. Used when opening a previously saved draft message
// for editing again.
// If no message is find, zero is returned, not an error.
async MessageFindMessageID(messageID: string): Promise<number> {
const fn: string = "MessageFindMessageID"
const paramTypes: string[][] = [["string"]]
const returnTypes: string[][] = [["int64"]]
const params: any[] = [messageID]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as number
}
// MessageCompose composes a message and saves it to the mailbox. Used for
// saving draft messages.
async MessageCompose(m: ComposeMessage, mailboxID: number): Promise<number> {
const fn: string = "MessageCompose"
const paramTypes: string[][] = [["ComposeMessage"],["int64"]]
const returnTypes: string[][] = [["int64"]]
const params: any[] = [m, mailboxID]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as number
}
// MessageSubmit sends a message by submitting it the outgoing email queue. The
// message is sent to all addresses listed in the To, Cc and Bcc addresses, without
// Bcc message header.
//
// If a Sent mailbox is configured, messages are added to it after submitting
// to the delivery queue. If Bcc addresses were present, a header is prepended
// to the message stored in the Sent mailbox.
async MessageSubmit(m: SubmitMessage): Promise<void> {
const fn: string = "MessageSubmit"
const paramTypes: string[][] = [["SubmitMessage"]]
const returnTypes: string[][] = []
const params: any[] = [m]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// MessageMove moves messages to another mailbox. If the message is already in
// the mailbox an error is returned.
async MessageMove(messageIDs: number[] | null, mailboxID: number): Promise<void> {
const fn: string = "MessageMove"
const paramTypes: string[][] = [["[]","int64"],["int64"]]
const returnTypes: string[][] = []
const params: any[] = [messageIDs, mailboxID]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// MessageDelete permanently deletes messages, without moving them to the Trash mailbox.
async MessageDelete(messageIDs: number[] | null): Promise<void> {
const fn: string = "MessageDelete"
const paramTypes: string[][] = [["[]","int64"]]
const returnTypes: string[][] = []
const params: any[] = [messageIDs]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// FlagsAdd adds flags, either system flags like \Seen or custom keywords. The
// flags should be lower-case, but will be converted and verified.
async FlagsAdd(messageIDs: number[] | null, flaglist: string[] | null): Promise<void> {
const fn: string = "FlagsAdd"
const paramTypes: string[][] = [["[]","int64"],["[]","string"]]
const returnTypes: string[][] = []
const params: any[] = [messageIDs, flaglist]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// FlagsClear clears flags, either system flags like \Seen or custom keywords.
async FlagsClear(messageIDs: number[] | null, flaglist: string[] | null): Promise<void> {
const fn: string = "FlagsClear"
const paramTypes: string[][] = [["[]","int64"],["[]","string"]]
const returnTypes: string[][] = []
const params: any[] = [messageIDs, flaglist]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// MailboxCreate creates a new mailbox.
async MailboxCreate(name: string): Promise<void> {
const fn: string = "MailboxCreate"
const paramTypes: string[][] = [["string"]]
const returnTypes: string[][] = []
const params: any[] = [name]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// MailboxDelete deletes a mailbox and all its messages.
async MailboxDelete(mailboxID: number): Promise<void> {
const fn: string = "MailboxDelete"
const paramTypes: string[][] = [["int64"]]
const returnTypes: string[][] = []
const params: any[] = [mailboxID]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// MailboxEmpty empties a mailbox, removing all messages from the mailbox, but not
// its child mailboxes.
async MailboxEmpty(mailboxID: number): Promise<void> {
const fn: string = "MailboxEmpty"
const paramTypes: string[][] = [["int64"]]
const returnTypes: string[][] = []
const params: any[] = [mailboxID]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// MailboxRename renames a mailbox, possibly moving it to a new parent. The mailbox
// ID and its messages are unchanged.
async MailboxRename(mailboxID: number, newName: string): Promise<void> {
const fn: string = "MailboxRename"
const paramTypes: string[][] = [["int64"],["string"]]
const returnTypes: string[][] = []
const params: any[] = [mailboxID, newName]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// CompleteRecipient returns autocomplete matches for a recipient, returning the
// matches, most recently used first, and whether this is the full list and further
// requests for longer prefixes aren't necessary.
async CompleteRecipient(search: string): Promise<[string[] | null, boolean]> {
const fn: string = "CompleteRecipient"
const paramTypes: string[][] = [["string"]]
const returnTypes: string[][] = [["[]","string"],["bool"]]
const params: any[] = [search]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as [string[] | null, boolean]
}
// MailboxSetSpecialUse sets the special use flags of a mailbox.
async MailboxSetSpecialUse(mb: Mailbox): Promise<void> {
const fn: string = "MailboxSetSpecialUse"
const paramTypes: string[][] = [["Mailbox"]]
const returnTypes: string[][] = []
const params: any[] = [mb]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// ThreadCollapse saves the ThreadCollapse field for the messages and its
// children. The messageIDs are typically thread roots. But not all roots
// (without parent) of a thread need to have the same collapsed state.
async ThreadCollapse(messageIDs: number[] | null, collapse: boolean): Promise<void> {
const fn: string = "ThreadCollapse"
const paramTypes: string[][] = [["[]","int64"],["bool"]]
const returnTypes: string[][] = []
const params: any[] = [messageIDs, collapse]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// ThreadMute saves the ThreadMute field for the messages and their children.
// If messages are muted, they are also marked collapsed.
async ThreadMute(messageIDs: number[] | null, mute: boolean): Promise<void> {
const fn: string = "ThreadMute"
const paramTypes: string[][] = [["[]","int64"],["bool"]]
const returnTypes: string[][] = []
const params: any[] = [messageIDs, mute]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// RecipientSecurity looks up security properties of the address in the
// single-address message addressee (as it appears in a To/Cc/Bcc/etc header).
async RecipientSecurity(messageAddressee: string): Promise<RecipientSecurity> {
const fn: string = "RecipientSecurity"
const paramTypes: string[][] = [["string"]]
const returnTypes: string[][] = [["RecipientSecurity"]]
const params: any[] = [messageAddressee]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as RecipientSecurity
}
// DecodeMIMEWords decodes Q/B-encoded words for a mime headers into UTF-8 text.
async DecodeMIMEWords(text: string): Promise<string> {
const fn: string = "DecodeMIMEWords"
const paramTypes: string[][] = [["string"]]
const returnTypes: string[][] = [["string"]]
const params: any[] = [text]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as string
}
// SettingsSave saves settings, e.g. for composing.
async SettingsSave(settings: Settings): Promise<void> {
const fn: string = "SettingsSave"
const paramTypes: string[][] = [["Settings"]]
const returnTypes: string[][] = []
const params: any[] = [settings]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
async RulesetSuggestMove(msgID: number, mbSrcID: number, mbDstID: number): Promise<[string, string, boolean, string, Ruleset | null]> {
const fn: string = "RulesetSuggestMove"
const paramTypes: string[][] = [["int64"],["int64"],["int64"]]
const returnTypes: string[][] = [["string"],["string"],["bool"],["string"],["nullable","Ruleset"]]
const params: any[] = [msgID, mbSrcID, mbDstID]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as [string, string, boolean, string, Ruleset | null]
}
async RulesetAdd(rcptTo: string, ruleset: Ruleset): Promise<void> {
const fn: string = "RulesetAdd"
const paramTypes: string[][] = [["string"],["Ruleset"]]
const returnTypes: string[][] = []
const params: any[] = [rcptTo, ruleset]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
async RulesetRemove(rcptTo: string, ruleset: Ruleset): Promise<void> {
const fn: string = "RulesetRemove"
const paramTypes: string[][] = [["string"],["Ruleset"]]
const returnTypes: string[][] = []
const params: any[] = [rcptTo, ruleset]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
async RulesetMessageNever(rcptTo: string, listID: string, msgFrom: string, toInbox: boolean): Promise<void> {
const fn: string = "RulesetMessageNever"
const paramTypes: string[][] = [["string"],["string"],["string"],["bool"]]
const returnTypes: string[][] = []
const params: any[] = [rcptTo, listID, msgFrom, toInbox]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
async RulesetMailboxNever(mailboxID: number, toMailbox: boolean): Promise<void> {
const fn: string = "RulesetMailboxNever"
const paramTypes: string[][] = [["int64"],["bool"]]
const returnTypes: string[][] = []
const params: any[] = [mailboxID, toMailbox]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// SSETypes exists to ensure the generated API contains the types, for use in SSE events.
async SSETypes(): Promise<[EventStart, EventViewErr, EventViewReset, EventViewMsgs, EventViewChanges, ChangeMsgAdd, ChangeMsgRemove, ChangeMsgFlags, ChangeMsgThread, ChangeMailboxRemove, ChangeMailboxAdd, ChangeMailboxRename, ChangeMailboxCounts, ChangeMailboxSpecialUse, ChangeMailboxKeywords, Flags]> {
const fn: string = "SSETypes"
const paramTypes: string[][] = []
const returnTypes: string[][] = [["EventStart"],["EventViewErr"],["EventViewReset"],["EventViewMsgs"],["EventViewChanges"],["ChangeMsgAdd"],["ChangeMsgRemove"],["ChangeMsgFlags"],["ChangeMsgThread"],["ChangeMailboxRemove"],["ChangeMailboxAdd"],["ChangeMailboxRename"],["ChangeMailboxCounts"],["ChangeMailboxSpecialUse"],["ChangeMailboxKeywords"],["Flags"]]
const params: any[] = []
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as [EventStart, EventViewErr, EventViewReset, EventViewMsgs, EventViewChanges, ChangeMsgAdd, ChangeMsgRemove, ChangeMsgFlags, ChangeMsgThread, ChangeMailboxRemove, ChangeMailboxAdd, ChangeMailboxRename, ChangeMailboxCounts, ChangeMailboxSpecialUse, ChangeMailboxKeywords, Flags]
}
}
export const defaultBaseURL = (function() {
let p = location.pathname
if (p && p[p.length - 1] !== '/') {
let l = location.pathname.split('/')
l = l.slice(0, l.length - 1)
p = '/' + l.join('/') + '/'
}
return location.protocol + '//' + location.host + p + 'api/'
})()
// NOTE: code below is shared between github.com/mjl-/sherpaweb and github.com/mjl-/sherpats.
// KEEP IN SYNC.
export const supportedSherpaVersion = 1
export interface Section {
Name: string
Docs: string
Functions: Function[]
Sections: Section[]
Structs: Struct[]
Ints: Ints[]
Strings: Strings[]
Version: string // only for top-level section
SherpaVersion: number // only for top-level section
SherpadocVersion: number // only for top-level section
}
export interface Function {
Name: string
Docs: string
Params: Arg[]
Returns: Arg[]
}
export interface Arg {
Name: string
Typewords: string[]
}
export interface Struct {
Name: string
Docs: string
Fields: Field[]
}
export interface Field {
Name: string
Docs: string
Typewords: string[]
}
export interface Ints {
Name: string
Docs: string
Values: {
Name: string
Value: number
Docs: string
}[] | null
}
export interface Strings {
Name: string
Docs: string
Values: {
Name: string
Value: string
Docs: string
}[] | null
}
export type NamedType = Struct | Strings | Ints
export type TypenameMap = { [k: string]: NamedType }
// verifyArg typechecks "v" against "typewords", returning a new (possibly modified) value for JSON-encoding.
// toJS indicate if the data is coming into JS. If so, timestamps are turned into JS Dates. Otherwise, JS Dates are turned into strings.
// allowUnknownKeys configures whether unknown keys in structs are allowed.
// types are the named types of the API.
export const verifyArg = (path: string, v: any, typewords: string[], toJS: boolean, allowUnknownKeys: boolean, types: TypenameMap, opts: ClientOptions): any => {
return new verifier(types, toJS, allowUnknownKeys, opts).verify(path, v, typewords)
}
export const parse = (name: string, v: any): any => verifyArg(name, v, [name], true, false, types, defaultOptions)
class verifier {
constructor(private types: TypenameMap, private toJS: boolean, private allowUnknownKeys: boolean, private opts: ClientOptions) {
}
verify(path: string, v: any, typewords: string[]): any {
typewords = typewords.slice(0)
const ww = typewords.shift()
const error = (msg: string) => {
if (path != '') {
msg = path + ': ' + msg
}
throw new Error(msg)
}
if (typeof ww !== 'string') {
error('bad typewords')
return // should not be necessary, typescript doesn't see error always throws an exception?
}
const w: string = ww
const ensure = (ok: boolean, expect: string): any => {
if (!ok) {
error('got ' + JSON.stringify(v) + ', expected ' + expect)
}
return v
}
switch (w) {
case 'nullable':
if (v === null || v === undefined && this.opts.nullableOptional) {
return v
}
return this.verify(path, v, typewords)
case '[]':
if (v === null && this.opts.slicesNullable || v === undefined && this.opts.slicesNullable && this.opts.nullableOptional) {
return v
}
ensure(Array.isArray(v), "array")
return v.map((e: any, i: number) => this.verify(path + '[' + i + ']', e, typewords))
case '{}':
if (v === null && this.opts.mapsNullable || v === undefined && this.opts.mapsNullable && this.opts.nullableOptional) {
return v
}
ensure(v !== null || typeof v === 'object', "object")
const r: any = {}
for (const k in v) {
r[k] = this.verify(path + '.' + k, v[k], typewords)
}
return r
}
ensure(typewords.length == 0, "empty typewords")
const t = typeof v
switch (w) {
case 'any':
return v
case 'bool':
ensure(t === 'boolean', 'bool')
return v
case 'int8':
case 'uint8':
case 'int16':
case 'uint16':
case 'int32':
case 'uint32':
case 'int64':
case 'uint64':
ensure(t === 'number' && Number.isInteger(v), 'integer')
return v
case 'float32':
case 'float64':
ensure(t === 'number', 'float')
return v
case 'int64s':
case 'uint64s':
ensure(t === 'number' && Number.isInteger(v) || t === 'string', 'integer fitting in float without precision loss, or string')
return '' + v
case 'string':
ensure(t === 'string', 'string')
return v
case 'timestamp':
if (this.toJS) {
ensure(t === 'string', 'string, with timestamp')
const d = new Date(v)
if (d instanceof Date && !isNaN(d.getTime())) {
return d
}
error('invalid date ' + v)
} else {
ensure(t === 'object' && v !== null, 'non-null object')
ensure(v.__proto__ === Date.prototype, 'Date')
return v.toISOString()
}
}
// We're left with named types.
const nt = this.types[w]
if (!nt) {
error('unknown type ' + w)
}
if (v === null) {
error('bad value ' + v + ' for named type ' + w)
}
if (structTypes[nt.Name]) {
const t = nt as Struct
if (typeof v !== 'object') {
error('bad value ' + v + ' for struct ' + w)
}
const r: any = {}
for (const f of t.Fields) {
r[f.Name] = this.verify(path + '.' + f.Name, v[f.Name], f.Typewords)
}
// If going to JSON also verify no unknown fields are present.
if (!this.allowUnknownKeys) {
const known: { [key: string]: boolean } = {}
for (const f of t.Fields) {
known[f.Name] = true
}
Object.keys(v).forEach((k) => {
if (!known[k]) {
error('unknown key ' + k + ' for struct ' + w)
}
})
}
return r
} else if (stringsTypes[nt.Name]) {
const t = nt as Strings
if (typeof v !== 'string') {
error('mistyped value ' + v + ' for named strings ' + t.Name)
}
if (!t.Values || t.Values.length === 0) {
return v
}
for (const sv of t.Values) {
if (sv.Value === v) {
return v
}
}
error('unknown value ' + v + ' for named strings ' + t.Name)
} else if (intsTypes[nt.Name]) {
const t = nt as Ints
if (typeof v !== 'number' || !Number.isInteger(v)) {
error('mistyped value ' + v + ' for named ints ' + t.Name)
}
if (!t.Values || t.Values.length === 0) {
return v
}
for (const sv of t.Values) {
if (sv.Value === v) {
return v
}
}
error('unknown value ' + v + ' for named ints ' + t.Name)
} else {
throw new Error('unexpected named type ' + nt)
}
}
}
export interface ClientOptions {
baseURL?: string
aborter?: {abort?: () => void}
timeoutMsec?: number
skipParamCheck?: boolean
skipReturnCheck?: boolean
slicesNullable?: boolean
mapsNullable?: boolean
nullableOptional?: boolean
csrfHeader?: string
login?: (reason: string) => Promise<string>
}
export interface AuthState {
token?: string // For csrf request header.
loginPromise?: Promise<void> // To let multiple API calls wait for a single login attempt, not each opening a login popup.
}
const _sherpaCall = async (baseURL: string, authState: AuthState, options: ClientOptions, paramTypes: string[][], returnTypes: string[][], name: string, params: any[]): Promise<any> => {
if (!options.skipParamCheck) {
if (params.length !== paramTypes.length) {
return Promise.reject({ message: 'wrong number of parameters in sherpa call, saw ' + params.length + ' != expected ' + paramTypes.length })
}
params = params.map((v: any, index: number) => verifyArg('params[' + index + ']', v, paramTypes[index], false, false, types, options))
}
const simulate = async (json: string) => {
const config = JSON.parse(json || 'null') || {}
const waitMinMsec = config.waitMinMsec || 0
const waitMaxMsec = config.waitMaxMsec || 0
const wait = Math.random() * (waitMaxMsec - waitMinMsec)
const failRate = config.failRate || 0
return new Promise<void>((resolve, reject) => {
if (options.aborter) {
options.aborter.abort = () => {
reject({ message: 'call to ' + name + ' aborted by user', code: 'sherpa:aborted' })
reject = resolve = () => { }
}
}
setTimeout(() => {
const r = Math.random()
if (r < failRate) {
reject({ message: 'injected failure on ' + name, code: 'server:injected' })
} else {
resolve()
}
reject = resolve = () => { }
}, waitMinMsec + wait)
})
}
// Only simulate when there is a debug string. Otherwise it would always interfere
// with setting options.aborter.
let json: string = ''
try {
json = window.localStorage.getItem('sherpats-debug') || ''
} catch (err) {}
if (json) {
await simulate(json)
}
const fn = (resolve: (v: any) => void, reject: (v: any) => void) => {
let resolve1 = (v: any) => {
resolve(v)
resolve1 = () => { }
reject1 = () => { }
}
let reject1 = (v: { code: string, message: string }) => {
if ((v.code === 'user:noAuth' || v.code === 'user:badAuth') && options.login) {
const login = options.login
if (!authState.loginPromise) {
authState.loginPromise = new Promise((aresolve, areject) => {
login(v.code === 'user:badAuth' ? (v.message || '') : '')
.then((token) => {
authState.token = token
authState.loginPromise = undefined
aresolve()
}, (err: any) => {
authState.loginPromise = undefined
areject(err)
})
})
}
authState.loginPromise
.then(() => {
fn(resolve, reject)
}, (err: any) => {
reject(err)
})
return
}
reject(v)
resolve1 = () => { }
reject1 = () => { }
}
const url = baseURL + name
const req = new window.XMLHttpRequest()
if (options.aborter) {
options.aborter.abort = () => {
req.abort()
reject1({ code: 'sherpa:aborted', message: 'request aborted' })
}
}
req.open('POST', url, true)
if (options.csrfHeader && authState.token) {
req.setRequestHeader(options.csrfHeader, authState.token)
}
if (options.timeoutMsec) {
req.timeout = options.timeoutMsec
}
req.onload = () => {
if (req.status !== 200) {
if (req.status === 404) {
reject1({ code: 'sherpa:badFunction', message: 'function does not exist' })
} else {
reject1({ code: 'sherpa:http', message: 'error calling function, HTTP status: ' + req.status })
}
return
}
let resp: any
try {
resp = JSON.parse(req.responseText)
} catch (err) {
reject1({ code: 'sherpa:badResponse', message: 'bad JSON from server' })
return
}
if (resp && resp.error) {
const err = resp.error
reject1({ code: err.code, message: err.message })
return
} else if (!resp || !resp.hasOwnProperty('result')) {
reject1({ code: 'sherpa:badResponse', message: "invalid sherpa response object, missing 'result'" })
return
}
if (options.skipReturnCheck) {
resolve1(resp.result)
return
}
let result = resp.result
try {
if (returnTypes.length === 0) {
if (result) {
throw new Error('function ' + name + ' returned a value while prototype says it returns "void"')
}
} else if (returnTypes.length === 1) {
result = verifyArg('result', result, returnTypes[0], true, true, types, options)
} else {
if (result.length != returnTypes.length) {
throw new Error('wrong number of values returned by ' + name + ', saw ' + result.length + ' != expected ' + returnTypes.length)
}
result = result.map((v: any, index: number) => verifyArg('result[' + index + ']', v, returnTypes[index], true, true, types, options))
}
} catch (err) {
let errmsg = 'bad types'
if (err instanceof Error) {
errmsg = err.message
}
reject1({ code: 'sherpa:badTypes', message: errmsg })
}
resolve1(result)
}
req.onerror = () => {
reject1({ code: 'sherpa:connection', message: 'connection failed' })
}
req.ontimeout = () => {
reject1({ code: 'sherpa:timeout', message: 'request timeout' })
}
req.setRequestHeader('Content-Type', 'application/json')
try {
req.send(JSON.stringify({ params: params }))
} catch (err) {
reject1({ code: 'sherpa:badData', message: 'cannot marshal to JSON' })
}
}
return await new Promise(fn)
}
}