// 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 } 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 MessageID: string } // 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. } // 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 // ". export interface SubmitMessage { From: string To?: string[] | null Cc?: string[] | null Bcc?: string[] | null Subject: string TextBody: string Attachments?: File[] | null ForwardAttachments: ForwardAttachments IsForward: boolean ResponseMessageID: number // If set, this was a reply or forward, based on IsForward. ReplyTo: string // If non-empty, Reply-To header to add to message. 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. } // 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. } // 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 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. 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, ... } // 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", } // 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,"Domain":true,"DomainAddressConfig":true,"Envelope":true,"EventStart":true,"EventViewChanges":true,"EventViewErr":true,"EventViewMsgs":true,"EventViewReset":true,"File":true,"Filter":true,"Flags":true,"ForwardAttachments":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,"SpecialUse":true,"SubmitMessage":true} export const stringsTypes: {[typename: string]: boolean} = {"AttachmentType":true,"CSRFToken":true,"Localpart":true,"SecurityResult":true,"ThreadMode":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":"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"]}]}, "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":"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":"ReplyTo","Docs":"","Typewords":["string"]},{"Name":"UserAgent","Docs":"","Typewords":["string"]},{"Name":"RequireTLS","Docs":"","Typewords":["nullable","bool"]},{"Name":"FutureRelease","Docs":"","Typewords":["nullable","timestamp"]}]}, "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"]}]}, "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":"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":""}]}, "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":""}]}, "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, 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, 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, SecurityResult: (v: any) => parse("SecurityResult", v) as SecurityResult, 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 { 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 { 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 { 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 { 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 { 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 { 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 } // 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. async MessageSubmit(m: SubmitMessage): Promise { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 } // 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('unknkown 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('unknkown 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 } export interface AuthState { token?: string // For csrf request header. loginPromise?: Promise // 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 => { 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((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) } }