jmap

package module
v0.4.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 12, 2023 License: MIT Imports: 11 Imported by: 7

README

go-jmap

A JMAP client library. Includes support for all core functionality (including PushSubscription and EventSource event streams), mail, smime-verify, and MDN specifications

Note: this library started as a fork of github.com/foxcpp/go-jmap It has since undergone massive refactoring and probably doesn't look very similar anymore, but many thanks to foxcpp for the initial work.

Usage

package main

import (
	"fmt"

	"git.sr.ht/~rockorager/go-jmap"
	"git.sr.ht/~rockorager/go-jmap/mail"
	"git.sr.ht/~rockorager/go-jmap/mail/email"
	"git.sr.ht/~rockorager/go-jmap/mail/mailbox"
)

func main() {
	// Create a new client. The SessionEndpoint must be specified for
	// initial connections.
	client := &jmap.Client{
		SessionEndpoint: "https://api.fastmail.com/jmap/session",
	}
	// Set the authentication mechanism. This also sets the HttpClient of
	// the jmap client
	client.WithAccessToken("my-access-token")

	// Authenticate the client. This gets a Session object. Session objects
	// are cacheable, and have their own state string clients can use to
	// decide when to refresh. The client can be initialized with a cached
	// Session object. If one isn't available, the first request will also
	// authenticate the client
	if err := client.Authenticate(); err != nil {
		// Handle the error
	}

	// Get the account ID of the primary mail account
	id := client.Session.PrimaryAccounts[mail.URI]

	// Create a new request
	req := &jmap.Request{}

	// Invoke a method. The CallID of this method will be returned to be
	// used when chaining calls
	req.Invoke(&mailbox.Get{
		Account: id,
	})

	// Invoke a changes call, let's save the callID and pass it to a Get
	// method
	callID := req.Invoke(&email.Changes{
		Account: id,
		SinceState: "some-known-state",
	})

	// Invoke a result reference call
	req.Invoke(&email.Get{
		Account: id,
		ReferenceIDs: &jmap.ResultReference{
			ResultOf: callID, // The CallID of the referenced method
			Name: "Email/changes", // The name of the referenced method
			Path: "/created", // JSON pointer to the location of the reference
		},
	})

	// Make the request
	resp, err := client.Do(req)
	if err != nil {
		// Handle the error
	}

	// Loop through the responses to invidividual invocations
	for _, inv := range resp.Responses {
		// Our result to individual calls is in the Args field of the
		// invocation
		switch r := inv.Args.(type) {
		case *mailbox.GetResponse:
			// A GetResponse contains a List of the objects
			// retrieved
			for _, mbox := range r.List {
				fmt.Printf("Mailbox name: %s", mbox.Name)
				fmt.Printf("Total email: %d", mbox.TotalEmails)
				fmt.Printf("Unread email: %d", mbox.UnreadEmails)
			}
		case *email.GetResponse:
			for _, eml := range r.List {
				fmt.Printf("Email subject: %s", eml.Subject)
			}
		}
		// There is a response in here to the Email/changes call, but we
		// don't care about the results since we passed them to the
		// Email/get call
	}
}

Status

Core (RFC 8620)

Complete

Mail (RFC 8621)

Complete

MDN (RFC 9007)

Complete

WebSocket (RFC 8887)

Not started

S/MIME (RFC 9219)

Complete

Documentation

Overview

Package jmap implements JMAP Core protocol as defined in RFC 8620 published on July 2019.

Documentation strings for most of the protocol objects are taken from (or based on) contents of RFC 8620 and is subject to the IETF Trust Provisions. See https://trustee.ietf.org/license-info for details.

Example

Basic usage of the client, with chaining of methods

// Create a new client. The SessionEndpoint must be specified for
// initial connections.
client := &jmap.Client{
	SessionEndpoint: "https://api.fastmail.com/jmap/session",
}
// Set the authentication mechanism. This also sets the HttpClient of
// the jmap client
client.WithAccessToken("my-access-token")

// Authenticate the client. This gets a Session object. Session objects
// are cacheable, and have their own state string clients can use to
// decide when to refresh. The client can be initialized with a cached
// Session object. If one isn't available, the first request will also
// authenticate the client
if err := client.Authenticate(); err != nil {
	// Handle the error
}

// Get the account ID of the primary mail account
id := client.Session.PrimaryAccounts[mail.URI]

// Create a new request
req := &jmap.Request{}

// Invoke a method. The CallID of this method will be returned to be
// used when chaining calls
req.Invoke(&mailbox.Get{
	Account: id,
})

// Invoke a changes call, let's save the callID and pass it to a Get
// method
callID := req.Invoke(&email.Changes{
	Account:    id,
	SinceState: "some-known-state",
})

// Invoke a result reference call
req.Invoke(&email.Get{
	Account: id,
	ReferenceIDs: &jmap.ResultReference{
		ResultOf: callID,          // The CallID of the referenced method
		Name:     "Email/changes", // The name of the referenced method
		Path:     "/created",      // JSON pointer to the location of the reference
	},
})

// Make the request
resp, err := client.Do(req)
if err != nil {
	// Handle the error
}

// Loop through the responses to invidividual invocations
for _, inv := range resp.Responses {
	// Our result to individual calls is in the Args field of the
	// invocation
	switch r := inv.Args.(type) {
	case *mailbox.GetResponse:
		// A GetResponse contains a List of the objects
		// retrieved
		for _, mbox := range r.List {
			fmt.Printf("Mailbox name: %s", mbox.Name)
			fmt.Printf("Total email: %d", mbox.TotalEmails)
			fmt.Printf("Unread email: %d", mbox.UnreadEmails)
		}
	case *email.GetResponse:
		for _, eml := range r.List {
			fmt.Printf("Email subject: %s", eml.Subject)
		}
	}
	// There is a response in here to the Email/changes call, but we
	// don't care about the results since we passed them to the
	// Email/get call
}
Output:

Example (Eventsource)

Example usage of an eventsource push notification connection

client := &jmap.Client{
	SessionEndpoint: "https://api.fastmail.com/jmap/session",
}
myHandlerFunc := func(change *jmap.StateChange) {
	// handle the change
}

// If we don't set the Events field, all events will be subscribed to
stream := &push.EventSource{
	Client:  client,
	Handler: myHandlerFunc,
}
if err := stream.Listen(); err != nil {
	// error occurs if the stream couldn't connect. Listen will
	// return when stream.Close is called
}
Output:

Index

Examples

Constants

View Source
const (
	// The ASCIINumeric collation is a simple collation intended for use
	// with arbitrary sized unsigned decimal integer numbers stored as octet
	// strings. US-ASCII digits (0x30 to 0x39) represent digits of the numbers.
	// Before converting from string to integer, the input string is truncated
	// at the first non-digit character. All input is valid; strings which do
	// not start with a digit represent positive infinity.
	//
	// Defined in RFC 4790.
	ASCIINumeric CollationAlgo = "i;ascii-numeric"

	// The ASCIICasemap collation is a simple collation which operates on
	// octet strings and treats US-ASCII letters case-insensitively. It provides
	// equality, substring and ordering operations. All input is valid. Note that
	// letters outside ASCII are not treated case- insensitively.
	//
	// Defined in RFC 4790.
	ASCIICasemap = "i;ascii-casemap"

	// The "i;unicode-casemap" collation is a simple collation which is
	// case-insensitive in its treatment of characters. It provides equality,
	// substring, and ordering operations. The validity test operation returns "valid"
	// for any input.
	//
	// This collation allows strings in arbitrary (and mixed) character sets,
	// as long as the character set for each string is identified and it is
	// possible to convert the string to Unicode. Strings which have an
	// unidentified character set and/or cannot be converted to Unicode are not
	// rejected, but are treated as binary.
	//
	// Defined in RFC 5051.
	UnicodeCasemap = "i;unicode-casemap"
)

Variables

This section is empty.

Functions

func RegisterCapability

func RegisterCapability(c Capability)

Register a Capability

func RegisterMethod

func RegisterMethod(name string, factory MethodResponseFactory)

Register a method. The Name parameter will be used when unmarshalling responses to call the responseConstructor, which should generate a pointer to an empty Response object of that method. This object will be returned in the result set (unless there is an error)

Types

type Account

type Account struct {
	// The ID of the account
	ID string `json:"-"`

	// A user-friendly string to show when presenting content from this
	// account, e.g. the email address representing the owner of the account.
	Name string `json:"name"`

	// This is true if the account belongs to the authenticated user, rather
	// than a group account or a personal account of another user that has been
	// shared with them.
	IsPersonal bool `json:"isPersonal"`

	// This is true if the entire account is read-only.
	IsReadOnly bool `json:"isReadOnly"`

	// The set of capability URIs for the methods supported in this account.
	// Each key is a URI for a capability that has methods you can use with
	// this account. The value for each of these keys is an object with further
	// information about the account’s permissions and restrictions with
	// respect to this capability, as defined in the capability’s
	// specification.
	Capabilities map[URI]Capability `json:"-"`

	// The raw JSON of accountCapabilities
	RawCapabilities map[URI]json.RawMessage `json:"accountCapabilities"`
}

An account is a collection of data. A single account may contain an arbitrary set of data types, for example a collection of mail, contacts and calendars.

See RFC 8620 section 1.6.2 for details.

func (*Account) UnmarshalJSON

func (a *Account) UnmarshalJSON(data []byte) error

type AddedItem

type AddedItem struct {
	ID    ID     `json:"id"`
	Index uint64 `json:"index"`
}

The id and index in the query results (in the new state) for every Foo that has been added to the results since the old state AND every Foo in the current results that was included in the removed array (due to a filter or sort based upon a mutable property).

If the sort and filter are both only on immutable properties and an upToId is supplied and exists in the results, any ids that were added but have a higher index than upToId SHOULD be omitted.

The array MUST be sorted in order of index, with the lowest index first.

type Capability

type Capability interface {
	// The URI of the capability, eg "urn:ietf:params:jmap:core"
	URI() URI

	// Generates a pointer to a new Capability object
	New() Capability
}

A Capability broadcasts that the server supports underlying methods

type Client

type Client struct {
	sync.Mutex
	// The HttpClient.Client to use for requests. The HttpClient.Client should handle
	// authentication. Calling WithBasicAuth or WithAccessToken on the
	// Client will set the HttpClient to one which uses authentication
	HttpClient *http.Client

	// The JMAP Session Resource Endpoint. If the client detects the Session
	// object needs refetching, it will automatically do so.
	SessionEndpoint string

	// the JMAP Session object
	Session *Session
}

A JMAP Client

func (*Client) Authenticate

func (c *Client) Authenticate() error

Authenticate authenticates the client and retrieves the Session object. Authenticate will be called automatically when Do is called if the Session object hasn't already been initialized. Call Authenticate before any requests if you need to access information from the Session object prior to the first request

func (*Client) Do

func (c *Client) Do(req *Request) (*Response, error)

Do performs a JMAP request and returns the response

func (*Client) Download

func (c *Client) Download(accountID ID, blobID ID) (io.ReadCloser, error)

Download downloads binary data by its Blob ID from the server.

func (*Client) Upload

func (c *Client) Upload(accountID ID, blob io.Reader) (*UploadResponse, error)

Upload sends binary data to the server and returns blob ID and some associated meta-data.

There are some caveats to keep in mind: - Server may return the same blob ID for multiple uploads of the same blob. - Blob ID may become invalid after some time if it is unused. - Blob ID is usable only by the uploader until it is used, even for shared accounts.

func (*Client) WithAccessToken

func (c *Client) WithAccessToken(token string) *Client

Set the HttpClient to a client which authenticates using the provided Access Token

func (*Client) WithBasicAuth

func (c *Client) WithBasicAuth(username string, password string) *Client

Set the HttpClient to a client which authenticates using the provided username and password

type CollationAlgo

type CollationAlgo string

type EventType

type EventType string

An EventType is the name of a Type provided by a capability which may be subscribed to using a PushSubscription or an EventSource connection. Each specification may define their own types and events

const AllEvents EventType = "*"

Subscribe to all events

type ID

type ID string

ID is a unique identifier assigned by the server. The character set must contain only ASCII alphanumerics, hyphen, or underscore and the ID must be between 1 and 255 octets long.

func (ID) MarshalJSON

func (id ID) MarshalJSON() ([]byte, error)

type Invocation

type Invocation struct {
	// The name of the method call or response
	Name string
	// Object containing the named arguments for the method or response
	Args interface{}
	// Arbitrary string set by client, echoed back with responses
	CallID string
}

An Invocation represents method calls and responses

func (*Invocation) MarshalJSON

func (i *Invocation) MarshalJSON() ([]byte, error)

func (*Invocation) UnmarshalJSON

func (i *Invocation) UnmarshalJSON(data []byte) error

type Method

type Method interface {
	// The name of the method, ie "Core/echo"
	Name() string

	// The JMAP capabilities required for the method, ie "urn:ietf:params:jmap:core"
	Requires() []URI
}

A JMAP method. The method object will be marshaled as the arguments to an invocation.

type MethodError

type MethodError struct {
	// The type of error that occurred. Always present
	Type string `json:"type,omitempty"`

	// Description is available on some method errors (notably,
	// invalidArguments)
	Description *string `json:"description,omitempty"`
}

A MethodError is returned when an error occurred while the server was processing a method. Instead of the Response of that method, a MethodError invocation will be in it's place

func (*MethodError) Error

func (m *MethodError) Error() string

type MethodResponse

type MethodResponse interface{}

A response to a method call

type MethodResponseFactory

type MethodResponseFactory func() MethodResponse

A Factory function which produces a new MethodResponse object

type Operator

type Operator string

Operator is used when constructing FilterOperator. It MUST be "AND", "OR", or "NOT"

const (
	// All of the conditions must match for the filter to match.
	OperatorAND Operator = "AND"

	// At least one of the conditions must match for the filter to match.
	OperatorOR Operator = "OR"

	// None of the conditions must match for the filter to match.
	OperatorNOT Operator = "NOT"
)

type Patch

type Patch map[string]interface{}

Patch represents a patch which can be used in a set.Update call. All paths MUST also conform to the following restrictions; if there is any violation, the update MUST be rejected with an invalidPatch error:

The pointer MUST NOT reference inside an array (i.e., you MUST NOT
insert/delete from an array; the array MUST be replaced in its entirety
instead). All parts prior to the last (i.e., the value after the final
slash) MUST already exist on the object being patched. There MUST NOT be
two patches in the Patch where the pointer of one is the prefix of
the pointer of the other, e.g., “alerts/1/offset” and “alerts”.

The keys are a JSON pointer path, and the value is the value to set the path to

type Request

type Request struct {
	// The context to make the request with
	Context context.Context `json:"-"`

	// The JMAP capabilities the request should use
	Using []URI `json:"using"`

	// A slice of methods the server will process. These will be processed
	// sequentially
	Calls []*Invocation `json:"methodCalls"`

	// A map of (client-specified) creation ID to the ID the server assigned
	// when a record was successfully created.
	CreatedIDs map[ID]ID `json:"createdIds,omitempty"`
}

func (*Request) Invoke

func (r *Request) Invoke(m Method) string

Invoke a method. Each call to Invoke will add the passed Method to the Request. The Requires method will be called and added to the request. The CallID of the Method is returned. CallIDs are assigned as the hex representation of the index of the call, eg "0"

type RequestError

type RequestError struct {
	// The type of request error, eg "urn:ietf:params:jmap:error:limit"
	Type string `json:"type"`

	// The HTTP status code of the response
	Status int `json:"status"`

	// The description of the error
	Detail string `json:"detail"`

	// If the error is of type ErrLimit, Limit will contain the name of the
	// limit the request would have exceeded
	Limit *string `json:"limit,omitempty"`
}

A RequestError occurs when there is an error with the HTTP request

func (*RequestError) Error

func (e *RequestError) Error() string

type Response

type Response struct {
	// An array of responses, in the same format as the Calls on the
	// Request object. The output of the methods will be added to the
	// methodResponses array in the same order as the methods are processed.
	Responses []*Invocation `json:"methodResponses"`

	// A map of (client-specified) creation id to the id the server assigned
	// when a record was successfully created.
	CreatedIDs map[ID]ID `json:"createdIds,omitempty"`

	// The current value of the “state” string on the JMAP Session object, as
	// described in section 2. Clients may use this to detect if this object
	// has changed and needs to be refetched.
	SessionState string `json:"sessionState"`
}

type ResultReference

type ResultReference struct {
	// The method call id (see Section 3.1.1) of a previous method call in
	// the current request.
	ResultOf string `json:"resultOf"`

	// The required name of a response to that method call.
	Name string `json:"name"`

	// A pointer into the arguments of the response selected via the name
	// and resultOf properties. This is a JSON Pointer [@!RFC6901], except
	// it also allows the use of * to map through an array (see the
	// description below).
	Path string `json:"path"`
}

To allow clients to make more efficient use of the network and avoid round trips, an argument to one method can be taken from the result of a previous method call in the same request.

To do this, the client prefixes the argument name with # (an octothorpe). The value is a ResultReference object as described below. When processing a method call, the server MUST first check the arguments object for any names beginning with #. If found, the result reference should be resolved and the value used as the “real” argument. The method is then processed as normal. If any result reference fails to resolve, the whole method MUST be rejected with an invalidResultReference error. If an arguments object contains the same argument name in normal and referenced form (e.g., foo and #foo), the method MUST return an invalidArguments error.

type Session

type Session struct {
	// An object specifying the capabilities of this server. Each key is a URI
	// for a capability supported by the server. The value for each of these
	// keys is an object with further information about the server’s
	// capabilities in relation to that capability.
	Capabilities map[URI]Capability `json:"-"`

	RawCapabilities map[URI]json.RawMessage `json:"capabilities"`

	// A map of account id to Account object for each account the user has
	// access to.
	Accounts map[ID]Account `json:"accounts"`

	// A map of capability URIs (as found in Capabilities) to the
	// account id to be considered the user’s main or default account for data
	// pertaining to that capability.
	PrimaryAccounts map[URI]ID `json:"primaryAccounts"`

	// The username associated with the given credentials, or the empty string
	// if none.
	Username string `json:"username"`

	// The URL to use for JMAP API requests.
	APIURL string `json:"apiUrl"`

	// The URL endpoint to use when downloading files, in RFC 6570 URI
	// Template (level 1) format.
	DownloadURL string `json:"downloadUrl"`

	// The URL endpoint to use when uploading files, in RFC 6570 URI
	// Template (level 1) format.
	UploadURL string `json:"uploadUrl"`

	// The URL to connect to for push events, as described in section 7.3, in
	// RFC 6570 URI Template (level 1) format.
	EventSourceURL string `json:"eventSourceUrl"`

	// A string representing the state of this object on the server. If the
	// value of any other property on the session object changes, this string
	// will change.
	//
	// The current value is also returned on the API Response object, allowing
	// clients to quickly determine if the session information has changed
	// (e.g. an account has been added or removed) and so they need to refetch
	// the object.
	State string `json:"state"`
}

func (*Session) UnmarshalJSON

func (s *Session) UnmarshalJSON(data []byte) error

type SetError

type SetError struct {
	// The type of SetError
	Type string `json:"type,omitempty"`

	// A description of the error to help with debugging that includes an
	// explanation of what the problem was. This is a non-localised string
	// and is not intended to be shown directly to end users.
	Description *string `json:"description,omitempty"`

	// Properties is available on InvalidProperties SetErrors and lists the
	// individual properties were
	Properties *[]string `json:"properties,omitempty"`
}

A SetError is returned in set calls for individual record changes

type StateChange

type StateChange struct {
	// This MUST be the string "StateChange"
	Type string `json:"@type"`

	// Map of AccountID to TypeState. Only changed values will be in the map
	Changed map[ID]TypeState `json:"changed"`
}

A StateChange object is sent to the client via Push mechanisms. It communicates when a change has occurred

type TypeState

type TypeState map[string]string

TypeState is a map of Foo object names ("Mailbox", "Email", etc) to state property which would be returned by a call to Foo/get

type URI

type URI string

URI is an identifier of a capability, eg "urn:ietf:params:jmap:core"

type UploadResponse

type UploadResponse struct {
	// The id of the account used for the call.
	Account ID `json:"accountId"`

	// The id representing the binary data uploaded. The data for this id is
	// immutable. The id only refers to the binary data, not any metadata.
	ID ID `json:"blobId"`

	// The media type of the file (as specified in RFC 6838, section 4.2) as
	// set in the Content-Type header of the upload HTTP request.
	Type string `json:"type"`

	// The size of the file in octets.
	Size uint64 `json:"size"`
}

UploadResponse is the object returned in response to blob upload.

Directories

Path Synopsis
Package mail is an implementation of JSON Metal Application Protocol (JMAP) for MAIL (RFC 8621)
Package mail is an implementation of JSON Metal Application Protocol (JMAP) for MAIL (RFC 8621)
mdn
Package mdn is an implementation of RFC 9007: Handling Message Disposition Notification with the JSON Meta Application Protocol (JMAP).
Package mdn is an implementation of RFC 9007: Handling Message Disposition Notification with the JSON Meta Application Protocol (JMAP).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL