mox/webaccount/api.ts
Mechiel Lukkien 42793834f8
add Content-Disposition and Filename to the payload of incoming webhooks
for each message part. The ContentDisposition value is the base value without
header key/value parameters. the Filename field is the likely filename of the
part. the different email clients encode filenames differently. there is a
standard mime mechanism from rfc 2231. and there is the q/b-word-encoding from
rfc 2047. instead of letting users of the webhook api deal with those
differences, we provide just the parsed filename.

for issue #258 by morki, thanks for reporting!
2024-12-06 14:19:39 +01:00

1015 lines
48 KiB
TypeScript

// NOTE: GENERATED by github.com/mjl-/sherpats, DO NOT MODIFY
namespace api {
export interface Account {
OutgoingWebhook?: OutgoingWebhook | null
IncomingWebhook?: IncomingWebhook | null
FromIDLoginAddresses?: string[] | null
KeepRetiredMessagePeriod: number
KeepRetiredWebhookPeriod: number
Domain: string
Description: string
FullName: string
Destinations?: { [key: string]: Destination }
SubjectPass: SubjectPass
QuotaMessageSize: number
RejectsMailbox: string
KeepRejects: boolean
AutomaticJunkFlags: AutomaticJunkFlags
JunkFilter?: JunkFilter | null // todo: sane defaults for junkfilter
MaxOutgoingMessagesPerDay: number
MaxFirstTimeRecipientsPerDay: number
NoFirstTimeSenderDelay: boolean
Routes?: Route[] | null
DNSDomain: Domain // Parsed form of Domain.
Aliases?: AddressAlias[] | null
}
export interface OutgoingWebhook {
URL: string
Authorization: string
Events?: string[] | null
}
export interface IncomingWebhook {
URL: string
Authorization: string
}
export interface Destination {
Mailbox: string
Rulesets?: Ruleset[] | null
FullName: string
}
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
}
// 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.
}
export interface SubjectPass {
Period: number // todo: have a reasonable default for this?
}
export interface AutomaticJunkFlags {
Enabled: boolean
JunkMailboxRegexp: string
NeutralMailboxRegexp: string
NotJunkMailboxRegexp: string
}
export interface JunkFilter {
Threshold: number
Onegrams: boolean
Twograms: boolean
Threegrams: boolean
MaxPower: number
TopWords: number
IgnoreWords: number
RareWords: number
}
export interface Route {
FromDomain?: string[] | null
ToDomain?: string[] | null
MinimumAttempts: number
Transport: string
FromDomainASCII?: string[] | null
ToDomainASCII?: string[] | null
}
export interface AddressAlias {
SubscriptionAddress: string
Alias: Alias // Without members.
MemberAddresses?: string[] | null // Only if allowed to see.
}
export interface Alias {
Addresses?: string[] | null
PostPublic: boolean
ListMembers: boolean
AllowMsgFrom: boolean
LocalpartStr: string // In encoded form.
Domain: Domain
ParsedAddresses?: AliasAddress[] | null // Matches addresses.
}
export interface AliasAddress {
Address: Address // Parsed address.
AccountName: string // Looked up.
Destination: Destination // Belonging to address.
}
// Address is a parsed email address.
export interface Address {
Localpart: Localpart
Domain: Domain // todo: shouldn't we accept an ip address here too? and merge this type into smtp.Path.
}
// Suppression is an address to which messages will not be delivered. Attempts to
// deliver or queue will result in an immediate permanent failure to deliver.
export interface Suppression {
ID: number
Created: Date
Account: string // Suppression applies to this account only.
BaseAddress: string // Unicode. Address with fictional simplified localpart: lowercase, dots removed (gmail), first token before any "-" or "+" (typical catchall separator).
OriginalAddress: string // Unicode. Address that caused this suppression.
Manual: boolean
Reason: string
}
// ImportProgress is returned after uploading a file to import.
export interface ImportProgress {
Token: string // For fetching progress, or cancelling an import.
}
// Outgoing is the payload sent to webhook URLs for events about outgoing deliveries.
export interface Outgoing {
Version: number // Format of hook, currently 0.
Event: OutgoingEvent // Type of outgoing delivery event.
DSN: boolean // If this event was triggered by a delivery status notification message (DSN).
Suppressing: boolean // If true, this failure caused the address to be added to the suppression list.
QueueMsgID: number // ID of message in queue.
FromID: string // As used in MAIL FROM, can be empty, for incoming messages.
MessageID: string // From Message-Id header, as set by submitter or us, with enclosing <>.
Subject: string // Of original message.
WebhookQueued: Date // When webhook was first queued for delivery.
SMTPCode: number // Optional, for errors only, e.g. 451, 550. See package smtp for definitions.
SMTPEnhancedCode: string // Optional, for errors only, e.g. 5.1.1.
Error: string // Error message while delivering, or from DSN from remote, if any.
Extra?: { [key: string]: string } // Extra fields set for message during submit, through webapi call or through X-Mox-Extra-* headers during SMTP submission.
}
// Incoming is the data sent to a webhook for incoming deliveries over SMTP.
export interface Incoming {
Version: number // Format of hook, currently 0.
From?: NameAddress[] | null // Message "From" header, typically has one address.
To?: NameAddress[] | null
CC?: NameAddress[] | null
BCC?: NameAddress[] | null // Often empty, even if you were a BCC recipient.
ReplyTo?: NameAddress[] | null // Optional Reply-To header, typically absent or with one address.
Subject: string
MessageID: string // Of Message-Id header, typically of the form "<random@hostname>", includes <>.
InReplyTo: string // Optional, the message-id this message is a reply to. Includes <>.
References?: string[] | null // Optional, zero or more message-ids this message is a reply/forward/related to. The last entry is the most recent/immediate message this is a reply to. Earlier entries are the parents in a thread. Values include <>.
Date?: Date | null // Time in "Date" message header, can be different from time received.
Text: string // Contents of text/plain and/or text/html part (if any), with "\n" line-endings, converted from "\r\n". Values are truncated to 1MB (1024*1024 bytes). Use webapi MessagePartGet to retrieve the full part data.
HTML: string
Structure: Structure // Parsed form of MIME message.
Meta: IncomingMeta // Details about message in storage, and SMTP transaction details.
}
export interface NameAddress {
Name: string // Optional, human-readable "display name" of the addressee.
Address: string // Required, email address.
}
export interface Structure {
ContentType: string // Lower case, e.g. text/plain.
ContentTypeParams?: { [key: string]: string } // Lower case keys, original case values, e.g. {"charset": "UTF-8"}.
ContentID: string // Can be empty. Otherwise, should be a value wrapped in <>'s. For use in HTML, referenced as URI `cid:...`.
ContentDisposition: string // Lower-case value, e.g. "attachment", "inline" or empty when absent. Without the key/value header parameters.
Filename: string // Filename for this part, based on "filename" parameter from Content-Disposition, or "name" from Content-Type after decoding.
DecodedSize: number // Size of content after decoding content-transfer-encoding. For text and HTML parts, this can be larger than the data returned since this size includes \r\n line endings.
Parts?: Structure[] | null // Subparts of a multipart message, possibly recursive.
}
export interface IncomingMeta {
MsgID: number // ID of message in storage, and to use in webapi calls like MessageGet.
MailFrom: string // Address used during SMTP "MAIL FROM" command.
MailFromValidated: boolean // Whether SMTP MAIL FROM address was SPF-validated.
MsgFromValidated: boolean // Whether address in message "From"-header was DMARC(-like) validated.
RcptTo: string // SMTP RCPT TO address used in SMTP.
DKIMVerifiedDomains?: string[] | null // Verified domains from DKIM-signature in message. Can be different domain than used in addresses.
RemoteIP: string // Where the message was delivered from.
Received: Date // When message was received, may be different from the Date header.
MailboxName: string // Mailbox where message was delivered to, based on configured rules. Defaults to "Inbox".
Automated: boolean // Whether this message was automated and should not receive automated replies. E.g. out of office or mailing list messages.
}
// TLSPublicKey is a public key for use with TLS client authentication based on the
// public key of the certificate.
export interface TLSPublicKey {
Fingerprint: string // Raw-url-base64-encoded Subject Public Key Info of certificate.
Created: Date
Type: string // E.g. "rsa-2048", "ecdsa-p256", "ed25519"
Name: string // Descriptive name to identify the key, e.g. the device where key is used.
NoIMAPPreauth: boolean // If set, new immediate authenticated TLS connections are not moved to "authenticated" state. For clients that don't understand it, and will try an authenticate command anyway.
CertDER?: string | null
Account: string // Key authenticates this account.
LoginAddress: string // Must belong to account.
}
export type CSRFToken = string
// 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
// OutgoingEvent is an activity for an outgoing delivery. Either generated by the
// queue, or through an incoming DSN (delivery status notification) message.
export enum OutgoingEvent {
// Message was accepted by a next-hop server. This does not necessarily mean the
// message has been delivered in the mailbox of the user.
EventDelivered = "delivered",
// Outbound delivery was suppressed because the recipient address is on the
// suppression list of the account, or a simplified/base variant of the address is.
EventSuppressed = "suppressed",
EventDelayed = "delayed", // A delivery attempt failed but delivery will be retried again later.
// Delivery of the message failed and will not be tried again. Also see the
// "Suppressing" field of [Outgoing].
EventFailed = "failed",
// Message was relayed into a system that does not generate DSNs. Should only
// happen when explicitly requested.
EventRelayed = "relayed",
// Message was accepted and is being delivered to multiple recipients (e.g. the
// address was an alias/list), which may generate more DSNs.
EventExpanded = "expanded",
EventCanceled = "canceled", // Message was removed from the queue, e.g. canceled by admin/user.
// An incoming message was received that was either a DSN with an unknown event
// type ("action"), or an incoming non-DSN-message was received for the unique
// per-outgoing-message address used for sending.
EventUnrecognized = "unrecognized",
}
export const structTypes: {[typename: string]: boolean} = {"Account":true,"Address":true,"AddressAlias":true,"Alias":true,"AliasAddress":true,"AutomaticJunkFlags":true,"Destination":true,"Domain":true,"ImportProgress":true,"Incoming":true,"IncomingMeta":true,"IncomingWebhook":true,"JunkFilter":true,"NameAddress":true,"Outgoing":true,"OutgoingWebhook":true,"Route":true,"Ruleset":true,"Structure":true,"SubjectPass":true,"Suppression":true,"TLSPublicKey":true}
export const stringsTypes: {[typename: string]: boolean} = {"CSRFToken":true,"Localpart":true,"OutgoingEvent":true}
export const intsTypes: {[typename: string]: boolean} = {}
export const types: TypenameMap = {
"Account": {"Name":"Account","Docs":"","Fields":[{"Name":"OutgoingWebhook","Docs":"","Typewords":["nullable","OutgoingWebhook"]},{"Name":"IncomingWebhook","Docs":"","Typewords":["nullable","IncomingWebhook"]},{"Name":"FromIDLoginAddresses","Docs":"","Typewords":["[]","string"]},{"Name":"KeepRetiredMessagePeriod","Docs":"","Typewords":["int64"]},{"Name":"KeepRetiredWebhookPeriod","Docs":"","Typewords":["int64"]},{"Name":"Domain","Docs":"","Typewords":["string"]},{"Name":"Description","Docs":"","Typewords":["string"]},{"Name":"FullName","Docs":"","Typewords":["string"]},{"Name":"Destinations","Docs":"","Typewords":["{}","Destination"]},{"Name":"SubjectPass","Docs":"","Typewords":["SubjectPass"]},{"Name":"QuotaMessageSize","Docs":"","Typewords":["int64"]},{"Name":"RejectsMailbox","Docs":"","Typewords":["string"]},{"Name":"KeepRejects","Docs":"","Typewords":["bool"]},{"Name":"AutomaticJunkFlags","Docs":"","Typewords":["AutomaticJunkFlags"]},{"Name":"JunkFilter","Docs":"","Typewords":["nullable","JunkFilter"]},{"Name":"MaxOutgoingMessagesPerDay","Docs":"","Typewords":["int32"]},{"Name":"MaxFirstTimeRecipientsPerDay","Docs":"","Typewords":["int32"]},{"Name":"NoFirstTimeSenderDelay","Docs":"","Typewords":["bool"]},{"Name":"Routes","Docs":"","Typewords":["[]","Route"]},{"Name":"DNSDomain","Docs":"","Typewords":["Domain"]},{"Name":"Aliases","Docs":"","Typewords":["[]","AddressAlias"]}]},
"OutgoingWebhook": {"Name":"OutgoingWebhook","Docs":"","Fields":[{"Name":"URL","Docs":"","Typewords":["string"]},{"Name":"Authorization","Docs":"","Typewords":["string"]},{"Name":"Events","Docs":"","Typewords":["[]","string"]}]},
"IncomingWebhook": {"Name":"IncomingWebhook","Docs":"","Fields":[{"Name":"URL","Docs":"","Typewords":["string"]},{"Name":"Authorization","Docs":"","Typewords":["string"]}]},
"Destination": {"Name":"Destination","Docs":"","Fields":[{"Name":"Mailbox","Docs":"","Typewords":["string"]},{"Name":"Rulesets","Docs":"","Typewords":["[]","Ruleset"]},{"Name":"FullName","Docs":"","Typewords":["string"]}]},
"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"]}]},
"Domain": {"Name":"Domain","Docs":"","Fields":[{"Name":"ASCII","Docs":"","Typewords":["string"]},{"Name":"Unicode","Docs":"","Typewords":["string"]}]},
"SubjectPass": {"Name":"SubjectPass","Docs":"","Fields":[{"Name":"Period","Docs":"","Typewords":["int64"]}]},
"AutomaticJunkFlags": {"Name":"AutomaticJunkFlags","Docs":"","Fields":[{"Name":"Enabled","Docs":"","Typewords":["bool"]},{"Name":"JunkMailboxRegexp","Docs":"","Typewords":["string"]},{"Name":"NeutralMailboxRegexp","Docs":"","Typewords":["string"]},{"Name":"NotJunkMailboxRegexp","Docs":"","Typewords":["string"]}]},
"JunkFilter": {"Name":"JunkFilter","Docs":"","Fields":[{"Name":"Threshold","Docs":"","Typewords":["float64"]},{"Name":"Onegrams","Docs":"","Typewords":["bool"]},{"Name":"Twograms","Docs":"","Typewords":["bool"]},{"Name":"Threegrams","Docs":"","Typewords":["bool"]},{"Name":"MaxPower","Docs":"","Typewords":["float64"]},{"Name":"TopWords","Docs":"","Typewords":["int32"]},{"Name":"IgnoreWords","Docs":"","Typewords":["float64"]},{"Name":"RareWords","Docs":"","Typewords":["int32"]}]},
"Route": {"Name":"Route","Docs":"","Fields":[{"Name":"FromDomain","Docs":"","Typewords":["[]","string"]},{"Name":"ToDomain","Docs":"","Typewords":["[]","string"]},{"Name":"MinimumAttempts","Docs":"","Typewords":["int32"]},{"Name":"Transport","Docs":"","Typewords":["string"]},{"Name":"FromDomainASCII","Docs":"","Typewords":["[]","string"]},{"Name":"ToDomainASCII","Docs":"","Typewords":["[]","string"]}]},
"AddressAlias": {"Name":"AddressAlias","Docs":"","Fields":[{"Name":"SubscriptionAddress","Docs":"","Typewords":["string"]},{"Name":"Alias","Docs":"","Typewords":["Alias"]},{"Name":"MemberAddresses","Docs":"","Typewords":["[]","string"]}]},
"Alias": {"Name":"Alias","Docs":"","Fields":[{"Name":"Addresses","Docs":"","Typewords":["[]","string"]},{"Name":"PostPublic","Docs":"","Typewords":["bool"]},{"Name":"ListMembers","Docs":"","Typewords":["bool"]},{"Name":"AllowMsgFrom","Docs":"","Typewords":["bool"]},{"Name":"LocalpartStr","Docs":"","Typewords":["string"]},{"Name":"Domain","Docs":"","Typewords":["Domain"]},{"Name":"ParsedAddresses","Docs":"","Typewords":["[]","AliasAddress"]}]},
"AliasAddress": {"Name":"AliasAddress","Docs":"","Fields":[{"Name":"Address","Docs":"","Typewords":["Address"]},{"Name":"AccountName","Docs":"","Typewords":["string"]},{"Name":"Destination","Docs":"","Typewords":["Destination"]}]},
"Address": {"Name":"Address","Docs":"","Fields":[{"Name":"Localpart","Docs":"","Typewords":["Localpart"]},{"Name":"Domain","Docs":"","Typewords":["Domain"]}]},
"Suppression": {"Name":"Suppression","Docs":"","Fields":[{"Name":"ID","Docs":"","Typewords":["int64"]},{"Name":"Created","Docs":"","Typewords":["timestamp"]},{"Name":"Account","Docs":"","Typewords":["string"]},{"Name":"BaseAddress","Docs":"","Typewords":["string"]},{"Name":"OriginalAddress","Docs":"","Typewords":["string"]},{"Name":"Manual","Docs":"","Typewords":["bool"]},{"Name":"Reason","Docs":"","Typewords":["string"]}]},
"ImportProgress": {"Name":"ImportProgress","Docs":"","Fields":[{"Name":"Token","Docs":"","Typewords":["string"]}]},
"Outgoing": {"Name":"Outgoing","Docs":"","Fields":[{"Name":"Version","Docs":"","Typewords":["int32"]},{"Name":"Event","Docs":"","Typewords":["OutgoingEvent"]},{"Name":"DSN","Docs":"","Typewords":["bool"]},{"Name":"Suppressing","Docs":"","Typewords":["bool"]},{"Name":"QueueMsgID","Docs":"","Typewords":["int64"]},{"Name":"FromID","Docs":"","Typewords":["string"]},{"Name":"MessageID","Docs":"","Typewords":["string"]},{"Name":"Subject","Docs":"","Typewords":["string"]},{"Name":"WebhookQueued","Docs":"","Typewords":["timestamp"]},{"Name":"SMTPCode","Docs":"","Typewords":["int32"]},{"Name":"SMTPEnhancedCode","Docs":"","Typewords":["string"]},{"Name":"Error","Docs":"","Typewords":["string"]},{"Name":"Extra","Docs":"","Typewords":["{}","string"]}]},
"Incoming": {"Name":"Incoming","Docs":"","Fields":[{"Name":"Version","Docs":"","Typewords":["int32"]},{"Name":"From","Docs":"","Typewords":["[]","NameAddress"]},{"Name":"To","Docs":"","Typewords":["[]","NameAddress"]},{"Name":"CC","Docs":"","Typewords":["[]","NameAddress"]},{"Name":"BCC","Docs":"","Typewords":["[]","NameAddress"]},{"Name":"ReplyTo","Docs":"","Typewords":["[]","NameAddress"]},{"Name":"Subject","Docs":"","Typewords":["string"]},{"Name":"MessageID","Docs":"","Typewords":["string"]},{"Name":"InReplyTo","Docs":"","Typewords":["string"]},{"Name":"References","Docs":"","Typewords":["[]","string"]},{"Name":"Date","Docs":"","Typewords":["nullable","timestamp"]},{"Name":"Text","Docs":"","Typewords":["string"]},{"Name":"HTML","Docs":"","Typewords":["string"]},{"Name":"Structure","Docs":"","Typewords":["Structure"]},{"Name":"Meta","Docs":"","Typewords":["IncomingMeta"]}]},
"NameAddress": {"Name":"NameAddress","Docs":"","Fields":[{"Name":"Name","Docs":"","Typewords":["string"]},{"Name":"Address","Docs":"","Typewords":["string"]}]},
"Structure": {"Name":"Structure","Docs":"","Fields":[{"Name":"ContentType","Docs":"","Typewords":["string"]},{"Name":"ContentTypeParams","Docs":"","Typewords":["{}","string"]},{"Name":"ContentID","Docs":"","Typewords":["string"]},{"Name":"ContentDisposition","Docs":"","Typewords":["string"]},{"Name":"Filename","Docs":"","Typewords":["string"]},{"Name":"DecodedSize","Docs":"","Typewords":["int64"]},{"Name":"Parts","Docs":"","Typewords":["[]","Structure"]}]},
"IncomingMeta": {"Name":"IncomingMeta","Docs":"","Fields":[{"Name":"MsgID","Docs":"","Typewords":["int64"]},{"Name":"MailFrom","Docs":"","Typewords":["string"]},{"Name":"MailFromValidated","Docs":"","Typewords":["bool"]},{"Name":"MsgFromValidated","Docs":"","Typewords":["bool"]},{"Name":"RcptTo","Docs":"","Typewords":["string"]},{"Name":"DKIMVerifiedDomains","Docs":"","Typewords":["[]","string"]},{"Name":"RemoteIP","Docs":"","Typewords":["string"]},{"Name":"Received","Docs":"","Typewords":["timestamp"]},{"Name":"MailboxName","Docs":"","Typewords":["string"]},{"Name":"Automated","Docs":"","Typewords":["bool"]}]},
"TLSPublicKey": {"Name":"TLSPublicKey","Docs":"","Fields":[{"Name":"Fingerprint","Docs":"","Typewords":["string"]},{"Name":"Created","Docs":"","Typewords":["timestamp"]},{"Name":"Type","Docs":"","Typewords":["string"]},{"Name":"Name","Docs":"","Typewords":["string"]},{"Name":"NoIMAPPreauth","Docs":"","Typewords":["bool"]},{"Name":"CertDER","Docs":"","Typewords":["nullable","string"]},{"Name":"Account","Docs":"","Typewords":["string"]},{"Name":"LoginAddress","Docs":"","Typewords":["string"]}]},
"CSRFToken": {"Name":"CSRFToken","Docs":"","Values":null},
"Localpart": {"Name":"Localpart","Docs":"","Values":null},
"OutgoingEvent": {"Name":"OutgoingEvent","Docs":"","Values":[{"Name":"EventDelivered","Value":"delivered","Docs":""},{"Name":"EventSuppressed","Value":"suppressed","Docs":""},{"Name":"EventDelayed","Value":"delayed","Docs":""},{"Name":"EventFailed","Value":"failed","Docs":""},{"Name":"EventRelayed","Value":"relayed","Docs":""},{"Name":"EventExpanded","Value":"expanded","Docs":""},{"Name":"EventCanceled","Value":"canceled","Docs":""},{"Name":"EventUnrecognized","Value":"unrecognized","Docs":""}]},
}
export const parser = {
Account: (v: any) => parse("Account", v) as Account,
OutgoingWebhook: (v: any) => parse("OutgoingWebhook", v) as OutgoingWebhook,
IncomingWebhook: (v: any) => parse("IncomingWebhook", v) as IncomingWebhook,
Destination: (v: any) => parse("Destination", v) as Destination,
Ruleset: (v: any) => parse("Ruleset", v) as Ruleset,
Domain: (v: any) => parse("Domain", v) as Domain,
SubjectPass: (v: any) => parse("SubjectPass", v) as SubjectPass,
AutomaticJunkFlags: (v: any) => parse("AutomaticJunkFlags", v) as AutomaticJunkFlags,
JunkFilter: (v: any) => parse("JunkFilter", v) as JunkFilter,
Route: (v: any) => parse("Route", v) as Route,
AddressAlias: (v: any) => parse("AddressAlias", v) as AddressAlias,
Alias: (v: any) => parse("Alias", v) as Alias,
AliasAddress: (v: any) => parse("AliasAddress", v) as AliasAddress,
Address: (v: any) => parse("Address", v) as Address,
Suppression: (v: any) => parse("Suppression", v) as Suppression,
ImportProgress: (v: any) => parse("ImportProgress", v) as ImportProgress,
Outgoing: (v: any) => parse("Outgoing", v) as Outgoing,
Incoming: (v: any) => parse("Incoming", v) as Incoming,
NameAddress: (v: any) => parse("NameAddress", v) as NameAddress,
Structure: (v: any) => parse("Structure", v) as Structure,
IncomingMeta: (v: any) => parse("IncomingMeta", v) as IncomingMeta,
TLSPublicKey: (v: any) => parse("TLSPublicKey", v) as TLSPublicKey,
CSRFToken: (v: any) => parse("CSRFToken", v) as CSRFToken,
Localpart: (v: any) => parse("Localpart", v) as Localpart,
OutgoingEvent: (v: any) => parse("OutgoingEvent", v) as OutgoingEvent,
}
// Account exports web API functions for the account web interface. All its
// methods are exported under api/. Function calls require valid HTTP
// Authentication credentials of a user.
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
}
// SetPassword saves a new password for the account, invalidating the previous password.
// Sessions are not interrupted, and will keep working. New login attempts must use the new password.
// Password must be at least 8 characters.
async SetPassword(password: string): Promise<void> {
const fn: string = "SetPassword"
const paramTypes: string[][] = [["string"]]
const returnTypes: string[][] = []
const params: any[] = [password]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// Account returns information about the account.
// StorageUsed is the sum of the sizes of all messages, in bytes.
// StorageLimit is the maximum storage that can be used, or 0 if there is no limit.
async Account(): Promise<[Account, number, number, Suppression[] | null]> {
const fn: string = "Account"
const paramTypes: string[][] = []
const returnTypes: string[][] = [["Account"],["int64"],["int64"],["[]","Suppression"]]
const params: any[] = []
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as [Account, number, number, Suppression[] | null]
}
// AccountSaveFullName saves the full name (used as display name in email messages)
// for the account.
async AccountSaveFullName(fullName: string): Promise<void> {
const fn: string = "AccountSaveFullName"
const paramTypes: string[][] = [["string"]]
const returnTypes: string[][] = []
const params: any[] = [fullName]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// DestinationSave updates a destination.
// OldDest is compared against the current destination. If it does not match, an
// error is returned. Otherwise newDest is saved and the configuration reloaded.
async DestinationSave(destName: string, oldDest: Destination, newDest: Destination): Promise<void> {
const fn: string = "DestinationSave"
const paramTypes: string[][] = [["string"],["Destination"],["Destination"]]
const returnTypes: string[][] = []
const params: any[] = [destName, oldDest, newDest]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// ImportAbort aborts an import that is in progress. If the import exists and isn't
// finished, no changes will have been made by the import.
async ImportAbort(importToken: string): Promise<void> {
const fn: string = "ImportAbort"
const paramTypes: string[][] = [["string"]]
const returnTypes: string[][] = []
const params: any[] = [importToken]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// Types exposes types not used in API method signatures, such as the import form upload.
async Types(): Promise<ImportProgress> {
const fn: string = "Types"
const paramTypes: string[][] = []
const returnTypes: string[][] = [["ImportProgress"]]
const params: any[] = []
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as ImportProgress
}
// SuppressionList lists the addresses on the suppression list of this account.
async SuppressionList(): Promise<Suppression[] | null> {
const fn: string = "SuppressionList"
const paramTypes: string[][] = []
const returnTypes: string[][] = [["[]","Suppression"]]
const params: any[] = []
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as Suppression[] | null
}
// SuppressionAdd adds an email address to the suppression list.
async SuppressionAdd(address: string, manual: boolean, reason: string): Promise<Suppression> {
const fn: string = "SuppressionAdd"
const paramTypes: string[][] = [["string"],["bool"],["string"]]
const returnTypes: string[][] = [["Suppression"]]
const params: any[] = [address, manual, reason]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as Suppression
}
// SuppressionRemove removes the email address from the suppression list.
async SuppressionRemove(address: string): Promise<void> {
const fn: string = "SuppressionRemove"
const paramTypes: string[][] = [["string"]]
const returnTypes: string[][] = []
const params: any[] = [address]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// OutgoingWebhookSave saves a new webhook url for outgoing deliveries. If url
// is empty, the webhook is disabled. If authorization is non-empty it is used for
// the Authorization header in HTTP requests. Events specifies the outgoing events
// to be delivered, or all if empty/nil.
async OutgoingWebhookSave(url: string, authorization: string, events: string[] | null): Promise<void> {
const fn: string = "OutgoingWebhookSave"
const paramTypes: string[][] = [["string"],["string"],["[]","string"]]
const returnTypes: string[][] = []
const params: any[] = [url, authorization, events]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// OutgoingWebhookTest makes a test webhook call to urlStr, with optional
// authorization. If the HTTP request is made this call will succeed also for
// non-2xx HTTP status codes.
async OutgoingWebhookTest(urlStr: string, authorization: string, data: Outgoing): Promise<[number, string, string]> {
const fn: string = "OutgoingWebhookTest"
const paramTypes: string[][] = [["string"],["string"],["Outgoing"]]
const returnTypes: string[][] = [["int32"],["string"],["string"]]
const params: any[] = [urlStr, authorization, data]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as [number, string, string]
}
// IncomingWebhookSave saves a new webhook url for incoming deliveries. If url is
// empty, the webhook is disabled. If authorization is not empty, it is used in
// the Authorization header in requests.
async IncomingWebhookSave(url: string, authorization: string): Promise<void> {
const fn: string = "IncomingWebhookSave"
const paramTypes: string[][] = [["string"],["string"]]
const returnTypes: string[][] = []
const params: any[] = [url, authorization]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// IncomingWebhookTest makes a test webhook HTTP delivery request to urlStr,
// with optional authorization header. If the HTTP call is made, this function
// returns non-error regardless of HTTP status code.
async IncomingWebhookTest(urlStr: string, authorization: string, data: Incoming): Promise<[number, string, string]> {
const fn: string = "IncomingWebhookTest"
const paramTypes: string[][] = [["string"],["string"],["Incoming"]]
const returnTypes: string[][] = [["int32"],["string"],["string"]]
const params: any[] = [urlStr, authorization, data]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as [number, string, string]
}
// FromIDLoginAddressesSave saves new login addresses to enable unique SMTP
// MAIL FROM addresses ("fromid") for deliveries from the queue.
async FromIDLoginAddressesSave(loginAddresses: string[] | null): Promise<void> {
const fn: string = "FromIDLoginAddressesSave"
const paramTypes: string[][] = [["[]","string"]]
const returnTypes: string[][] = []
const params: any[] = [loginAddresses]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// KeepRetiredPeriodsSave saves periods to save retired messages and webhooks.
async KeepRetiredPeriodsSave(keepRetiredMessagePeriod: number, keepRetiredWebhookPeriod: number): Promise<void> {
const fn: string = "KeepRetiredPeriodsSave"
const paramTypes: string[][] = [["int64"],["int64"]]
const returnTypes: string[][] = []
const params: any[] = [keepRetiredMessagePeriod, keepRetiredWebhookPeriod]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// AutomaticJunkFlagsSave saves settings for automatically marking messages as
// junk/nonjunk when moved to mailboxes matching certain regular expressions.
async AutomaticJunkFlagsSave(enabled: boolean, junkRegexp: string, neutralRegexp: string, notJunkRegexp: string): Promise<void> {
const fn: string = "AutomaticJunkFlagsSave"
const paramTypes: string[][] = [["bool"],["string"],["string"],["string"]]
const returnTypes: string[][] = []
const params: any[] = [enabled, junkRegexp, neutralRegexp, notJunkRegexp]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// JunkFilterSave saves junk filter settings. If junkFilter is nil, the junk filter
// is disabled. Otherwise all fields except Threegrams are stored.
async JunkFilterSave(junkFilter: JunkFilter | null): Promise<void> {
const fn: string = "JunkFilterSave"
const paramTypes: string[][] = [["nullable","JunkFilter"]]
const returnTypes: string[][] = []
const params: any[] = [junkFilter]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// RejectsSave saves the RejectsMailbox and KeepRejects settings.
async RejectsSave(mailbox: string, keep: boolean): Promise<void> {
const fn: string = "RejectsSave"
const paramTypes: string[][] = [["string"],["bool"]]
const returnTypes: string[][] = []
const params: any[] = [mailbox, keep]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
async TLSPublicKeys(): Promise<TLSPublicKey[] | null> {
const fn: string = "TLSPublicKeys"
const paramTypes: string[][] = []
const returnTypes: string[][] = [["[]","TLSPublicKey"]]
const params: any[] = []
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as TLSPublicKey[] | null
}
async TLSPublicKeyAdd(loginAddress: string, name: string, noIMAPPreauth: boolean, certPEM: string): Promise<TLSPublicKey> {
const fn: string = "TLSPublicKeyAdd"
const paramTypes: string[][] = [["string"],["string"],["bool"],["string"]]
const returnTypes: string[][] = [["TLSPublicKey"]]
const params: any[] = [loginAddress, name, noIMAPPreauth, certPEM]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as TLSPublicKey
}
async TLSPublicKeyRemove(fingerprint: string): Promise<void> {
const fn: string = "TLSPublicKeyRemove"
const paramTypes: string[][] = [["string"]]
const returnTypes: string[][] = []
const params: any[] = [fingerprint]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
async TLSPublicKeyUpdate(pubKey: TLSPublicKey): Promise<void> {
const fn: string = "TLSPublicKeyUpdate"
const paramTypes: string[][] = [["TLSPublicKey"]]
const returnTypes: string[][] = []
const params: any[] = [pubKey]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
}
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)
}
}