mirror of
https://github.com/mjl-/mox.git
synced 2024-12-26 16:33:47 +03:00
879477a01f
before this change, when archiving, we would move all messages from the thread that are in the same mailbox as that of the response message to the archive mailbox. so if the message that was being responsed to was already in the archive mailbox, the message would be moved from archive mailbox to archive mailbox, resulting in an error. with this change, when archiving, we move the thread messages that are in the same mailbox as is currently open (independent of the mailbox the message lives in, a common situation in the threading view). if there is no open mailbox (search results), we still use the mailbox of the message being responded to as reference. with this new approach, we won't get errors moving a message to an archive mailbox when it's already there. well, you can still get that error, but then you've got the archive mailbox open, or you're in a search result and responding to an archived message. the error should at least help understand that nothing is happening. we are only moving the messages from one active/reference mailbox because we don't want to move messages from the thread that are in the Sent mailbox, and we also don't want to move duplicate messages (cross-posts to mailing lists) that are in other mailboxes. moving only the messages from the current active mailbox seems safe, and should do what is what users would expect most of the time. for issue #233 by mattfbacon, thanks for reporting!
1456 lines
82 KiB
TypeScript
1456 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.
|
|
ArchiveReferenceMailboxID: number // If ArchiveThread is set, thread messages from this mailbox ID are moved to the archive mailbox ID. E.g. of Inbox.
|
|
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.
|
|
ShowHTML: boolean // Show HTML version of message by default, instead of plain text.
|
|
}
|
|
|
|
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 {
|
|
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":"ArchiveReferenceMailboxID","Docs":"","Typewords":["int64"]},{"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"]},{"Name":"ShowHTML","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":"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 single-use 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)
|
|
}
|
|
|
|
}
|