jmap

package module
v0.4.6 Latest Latest
Warning

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

Go to latest
Published: Feb 15, 2024 License: MIT Imports: 11 Imported by: 3

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.

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 (
	// Defined in RFC 4790.
	ASCIINumeric CollationAlgo = "i;ascii-numeric"

	// Defined in RFC 4790.
	ASCIICasemap = "i;ascii-casemap"

	// 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"`

	// True if this account belongs to the authenticated user
	IsPersonal bool `json:"isPersonal"`

	IsReadOnly bool `json:"isReadOnly"`

	// The set of capability URIs for the methods supported in this account.
	Capabilities map[URI]Capability `json:"-"`

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

An account is a collection of data the authenticated user has access to

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"`
}

AddedItem is an item that has been added to the results of a Query

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) DownloadWithContext added in v0.4.6

func (c *Client) DownloadWithContext(
	ctx context.Context, accountID ID, blobID ID,
) (io.ReadCloser, error)

DownloadWithContext 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) UploadWithContext added in v0.4.6

func (c *Client) UploadWithContext(
	ctx context.Context, accountID ID, blob io.Reader,
) (*UploadResponse, error)

UploadWithContext 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

EventType is the type of object the Event is for ("Mailbox", "Email")

const AllEvents EventType = "*"

Subscribe to all events

type ID

type ID string

ID is a unique identifier assigned by the server

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 is a JMAP patch object which can be used in set.Update calls. The keys are json pointer paths, 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 {
	// Responses are the responses to the request, in the same order that
	// the request was made
	Responses []*Invocation `json:"methodResponses"`

	// A map of client-specified ID to server-assigned ID
	CreatedIDs map[ID]ID `json:"createdIds,omitempty"`

	// SessionState is the current state of the Session
	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"`
}

ResultReference is a reference to a previous Invocations' result

type Session

type Session struct {
	// Capabilities specifies the capabililities the server has.
	Capabilities map[URI]Capability `json:"-"`

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

	Accounts map[ID]Account `json:"accounts"`

	// PrimaryAccounts maps a Capability to the primary account associated
	// with it
	PrimaryAccounts map[URI]ID `json:"primaryAccounts"`

	// The username associated with the given credentials
	Username string `json:"username"`

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

	// The URL endpoint to use when downloading files
	DownloadURL string `json:"downloadUrl"`

	// The URL endpoint to use when uploading files
	UploadURL string `json:"uploadUrl"`

	// The URL to connect to for push events
	EventSourceURL string `json:"eventSourceUrl"`

	// A string representing the state of this object on the server
	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

Jump to

Keyboard shortcuts

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