The Go-Fed ActivityPub Tutorial

This focuses on how to use go-fed/activity version 0.x to create an application that implements ActivityPub in Go.

You will also see these scattered thoughout the tutorial:

Table Of Contents

  1. Prerequisites
  2. Introduction
  3. Library Layout
  4. ActivityStreams: Types & Properties
  5. ActivityStreams: Serialization
  6. ActivityPub: The Two Protocols
  7. ActivityPub: SocialAPI (Client to Server)
  8. ActivityPub: FederateAPI (Server to Server)
  9. ActivityPub: Both APIs (Client to Server & Server to Server)
  10. References

Prerequisites

This tutorial assumes that you are familiar with the Go programming language. If not, I highly encourage you to take a look at the Tour of Go.

No prior knowledge of ActivityPub is required. While this tutorial will neither contain a complete nor deep overview of the protocol, it will contain enough pieces to get you started with the basics. Those already familiar with the protocols may find such pieces uninteresting.

If at the end you are motivated to explore further, I have provided links to primary source documents and repositories.

Introduction

Welcome! By the time you have finished with this section you will have a basic and abstract understanding of how ActivityPub works. You will have enough contextual knowledge to help make decisions when using this library. Those already familiar with ActivityPub and ActivityStreams can safely skip this section.

Let's begin!

ActivityPub is actually two protocols in one, but both govern application behavior. The SocialAPI sets the rules for client-to-server interactions. When implemented, an ActivityPub client could interact with any other ActivityPub server. This lets a user use, for example, a single client on their phone to talk to their accounts on different kinds of ActivityPub servers. The FederateAPI governs how two servers share data which lets users on all federating servers communicate with each other. Users on microblogs, photoblogs, video sites, and your future application can all interact!

Despite providing both protocols, ActivityPub does not require both be used. For example, Mastodon supports the FederateAPI but not the SocialAPI. It is up to you and your application's needs whether you want to use one, the other, or both!

To communicate, ActivityPub shares data in the ActivityStreams format. A piece of ActivityStream data is encoded as JSON when examined on the wire. However, it is actually built on top of JSON-LD which is a subset of JSON. To summarize a very deep topic like JSON-LD, it does two things. One: it is a rich data format (RDF) on top of JSON which effectively results in the JSON's schema being embedded within each JSON message. Two: it allows pieces of JSON data to refer to each other, resulting in a web of data that can be traversed like a graph.

This means when sending and receiving ActivityStreams via ActivityPub, your application is actually building a graph of data. As new data is generated, the graph gets bigger. The idea of "pointers" to other data looks like URLs, but they are technically IRIs.

The ActivityStreams specification doesn't just dictate a data format that is a subset of JSON-LD, it also specifies Core and Extended types of data. These are then used by ActivityPub to govern some basic behaviors in the SocialAPI and FederateAPI. Specific examples of these Core and Extended data types will be examined later on in the tutorial.

However, your application isn't limited to handling only Core and Extended ActivityStream data types. Since it is built on top of JSON-LD, the ActivityStreams vocabulary supports extensions beyond the Core and Extended types. However, this will be outside the scope of this tutorial.

Let's also go over some things that ActivityPub does not support out of the box. There may be community conventions around these topics, the details of which are also outside the scope of this tutorial.

The security protocols for authorization and authentication is not standardized. Some choices I am aware of are OAuth 2.0 and HTTP Signatures.

Spam handling and blocking federating peers is not-standardized and usually implemented as an administrative application feature.

The way to fetch a raw ActivityStream versus its human-readable HTML representation in static servers is not currently standardized.

You're now knowledgeable enough to dive into the library!

Library Layout

The github.com/go-fed/activity library is split into two different core libraries. Other libraries within the repository are either optional or used in code-generation.

The first core library is github.com/go-fed/activity/vocab which contains the Core and Extended ActivityStreams types. The APIs of these types are large, but as we will see it is by design. This library is code generated.

The second core library is github.com/go-fed/activity/pub which implements the ActivityPub protocol. It heavily relies on your application satisfying interfaces that are not trivial to implement. In exchange for implementing these interfaces, the pub types are very easy to hook into new or existing code.

These two libraries are all you need during daily development. For completeness I will go ahead and mention a few packages in the repository.

An optional library built on top of vocab is github.com/go-fed/activity/streams. It tries to reduce the complexity of the vocab API, but in further discussions with others I belive it has failed in its mission of being a simpler design. The streams package provides a Resolver type which is recommended for use, but the use of its types is discouraged and the types may be removed in a major version release.

Everything under tools is used to code-generate the vocab and streams implementation. It is ignorable for daily development.

ActivityStreams: Types & Properties

The vocab package provides Go struct types that are equivalent to the ActivityStream types. It contains both Core and Extended ActivityStream types. They can be broken down into three categories: Actor, Activity, and Object/Link. All of these concepts dictate what data they are allowed to have and how to interpret such data. These are known in the ActivityStreams parlance as properties.

Since the Object type is a popular choice for other types to extend from, we will address it and its properties first.

A Deep Dive Example: The Object Type

An Object represents an abstract idea on its own. Applications typically use other types that extend from it, inheriting all of its properties, to share data. But it is still OK to use the Object directly. So let's look at some of the most commonly used properties of an Object:

An Object is created and its properties set or accessed in the following example:

// Create an 'Object' type. Types are not copy-safe.
obj := &vocab.Object{}

// Add a value for the property 'name'
obj.AppendNameString("Hello World")

// Add a value for the property 'content'
obj.AppendContentString("Here is some <pre>HTML</pre> content")

// Add two 'to' recipients
addisonIRI, _ := url.Parse("https://www.example.com/addison")
obj.AppendToIRI(addisonIRI)
dakotaIRI, _ := url.Parse("https://www.example.com/dakota")
obj.AppendToIRI(dakotaIRI)

// Now let's handle all of the possible values of 'to'
for i := 0; i < obj.ToLen(); i++ {
	if obj.IsToObject(i) {
		// Call obj.GetToObject(i)
	} else if obj.IsToLink(i) {
		// Call obj.GetToLink(i)
	} else if obj.IsToIRI(i) {
		// Call obj.GetToIRI(i)
	}
}

Each property has quite a few methods to deal with it. There are two kinds of properties, which determines the methods available: properties that can have multiple values and properties that can have at most one value. Properties that can have multiple values are known as "non-functional" properties in the ActivityStream specification. Properties that can have at most one value are called "functional" properties.

For example, the to, name, and content properties are all non-functional properties. Using to as an example, for a non-functional property you need to be able to:

The methods available for the to property therefore are:

// Append different types of 'to' values
func (o *Object) AppendToObject(v ObjectType)
func (o *Object) AppendToLink(v LinkType)
func (o *Object) AppendToIRI(v *url.URL)

// Prepend different types of 'to' values
func (o *Object) PrependToObject(v ObjectType)
func (o *Object) PrependToLink(v LinkType)
func (o *Object) PrependToIRI(v *url.URL)

// Determine the number of 'to' values
func (o *Object) ToLen() (l int)

// Determine what type a 'to' value is at a specific index
func (o *Object) IsToObject(index int) (ok bool)
func (o *Object) IsToLink(index int) (ok bool)
func (o *Object) IsToIRI(index int) (ok bool)

// Get a 'to' value at a specific index of the correct type
func (o *Object) GetToObject(index int) (v ObjectType)
func (o *Object) GetToLink(index int) (v LinkType)
func (o *Object) GetToIRI(index int) (v *url.URL)

// Remove a 'to' value at a specific index of the correct type
func (o *Object) RemoveToObject(index int)
func (o *Object) RemoveToLink(index int)
func (o *Object) RemoveToIRI(index int)

And for a functional property like source:

// Determine what type a 'source' value is at a specific index.
// Note that getting and setting a property with one type is
// so common that method names omit ending the method with
// the type name if the only other acceptable type is an IRI
// In this case, "Object" is the only acceptable type.
func (o *Object) IsSource() (ok bool)
func (o *Object) IsSourceIRI() (ok bool)

// Get a 'source' value at a specific index of the correct type
func (o *Object) GetSource() (v ObjectType)
func (o *Object) GetSourceIRI() (v *url.URL)

// Set different types of 'source' values
func (o *Object) SetSource(v ObjectType)
func (o *Object) SetSourceIRI(v *url.URL)

Repeat this for numerous properties on many different types, and the API grows quite large. The size is large despite the restrictions ActivityStreams imposes on top of JSON-LD, which is very flexible. If there is one specification I would recommend keeping open while writing an ActivityStream application, it would be the ActivityStreams Vocabulary. It will help you learn more ActivityStreams types, ActivityStreams properties, and the permitted value types of those properties.

There are also interfaces provided in the vocab package for each type. The struct version of each type satisfies its corresponding interface. It shares the same name as the ActivityStreams type with the world "Type" appended:

// Setting concrete 'Object' struct to 'ObjectType' interface
var objInterface vocab.ObjectType = &vocab.Object{}

While interfaces are provided for all classes, unrelated types may satisfy each other's interfaces. This is because unrelated types may share the same properties. The only interfaces that will filter correct children types are:

Unfortunately, the Activity type requires inspecting the actual type property. The vocab library provides such a function for you:

var objType vocab.ObjectType = ...
if vocab.IsActivityType(objType) {
	// 'objType' is an ActivityStreams activity.
}

The last piece of advice for properties I have is on IRIs. They take the form of URLs. They can almost always take the place of actual data, and are basically a pointer-over-HTTP. Whenever you see an IRI, know that fetching that URL will "fill in" that piece of data from your peer's server. Whenever you send out data with an IRI, you are telling your peers' servers that you will host that ActivityStream data at that IRI location.

In the next sections, I will go over more types and properties from the ActivityStreams Vocabulary specification.

Actor Types

An actor type represents a who or what is doing an Activity. These do not have to be human beings and could be an organization or another computer. The types are:

All of these types have the same properties as Object.

Activity Types

The meat of ActivityPub, an activity type represents what action is being done. These are the root types that are shared between servers in ActivityPub. Some common activiites are:

Some key additional properties of Activity are:

Object/Link Types

The Object and Link types both describe traditional pieces of application data. These are the things that actors apply activities to. Note that the Object and Link types are specifically made disjoint in the specification, which just means it is illegal to try to combine the two types together. Since there are very few interesting types that extend Link, let's examine some that extend Object:

Most of these have no new properties besides the ones already provided by Object. Profile and Tombstone have some additional useful properties. When in doubt, check the ActivityStreams Vocabulary specification.

ActivityStreams: Serialization

Conversion between a map[string]interface{} and a concrete type is handled by a combination of the vocab and streams packages. Serialization between raw JSON ([]byte) and map[string]interface{} is still handled by the standard encoding/json library. This means the vocab and streams packages are independent of the wire encoding.

Fortunately, all of this serialization, deserialization, and conversion is routinely handled by the pub package, and won't be needed in your routine use of that library. However, it is useful to know if you want to convert and encode these types as part of your own database schemes (for example, as part of an application's Postgres JSON type).

Serialization

When given a vocab concrete type, an Activity for example, it will satisfy the Serializer interface. It doesn't encode directly to JSON, but instead to a map[string]interface{} which can then be used to encode to JSON:

type Serializer interface {
	Serialize() (m map[string]interface{}, e error)
}

Storing this JSON can then be done like:

v := &vocab.Activity{}
m, _ := v.Serialize()
b, _ := json.Marshal(m)

Deserialization

If given a raw set of []byte, both the vocab and streams packages are used to obtain a concrete type. First, decode the bytes to a map[string]interface{} (with encoding/json if JSON). OK, that was easy. Now, each vocab type implements a Deserializer interface:

type Deserializer interface {
	Deserialize(m map[string]interface{}) (e error)
}

But the difficult question is: which vocab type should this map deserialize to? Activity? Note?

Enter Resolver in the streams package. It detects the correct type and calls a callback into your code with the concrete type deserialized:

type Resolver struct {
	ObjectCallback func(*Object) error
	LinkCallback func(*Link) error
	ActivityCallback func(*Activity) error
	CreateCallback func(*Create) error
	AnyObjectCallback func(*vocab.ObjectType) error
	AnyLinkCallback func(*vocab.LinkType) error
	AnyActivityCallback func(*vocab.ActivityType) error
	// etc...
}

func (t *Resolver) Deserialize(m map[string]interface{}) (err error) {
	// Call the correct callbacks.
}

You do not need to set all of these callbacks to use the Resolver. Only set the ones you care about. It is OK for ActivityPub implementations to ignore types that they are not meant to handle. The giant list of all types is provided for flexibility, but can definitely seem intimidating.

The specific callbacks use the objects from the stream package, not the objects from the vocab package. On the other hand, the "Any" callbacks use interfaces from the vocab package.

The callbacks with the "Any" prefix will be called in addition to the specific callback, based on the "extends" concept defined in the ActivityStreams specification. For example, when deserializing a Note, the NoteCallback and AnyObjectCallback will be called since a Note ultimately is extended from an Object.

Putting all of this together, deserializing would look like:

func myNoteHandler(note *streams.Note) error {
	// Use 'note' in your application
}

func myDeserializeFunc(b []byte) error {
	r := &streams.Resolver {
		NoteCallback: myNoteHandler,
	}
	var m map[string]interface{}
	if e := json.Unmarshal(b, &m); e != nil {
		return e
	}
	return r.Deserialize(m)
}

ActivityPub: The Two Protocols

While ActivityStreams defines the data structures being passed around, the ActivityPub protocol defines key behaviors to get very different applications to behave well together. While the SocialAPI and FederateAPI define different behaviors, there are some shared concepts. We will go over these concepts in this section.

Before we begin, understand that abstracting the ActivityPub protocol from an application is very difficult. However, pub is able to handle a lot of the default lifting for you, and follows a "just works" mentality while providing you with as much specification-compliant flexibility as possible. Don't get discouraged from rapidly prototyping, but understand this takes some serious effort to get right. And this library is geared towards the latter.

There are two key things your application needs to accomplish, which this library will do for you:

Before we can tackle these big items, we need to implement some interfaces so that the library can do these default behaviors for you.

Firstly, the application needs to be able to fetch the current time. Rather than hardcode calls to time.Now, you will need to provide a simple clock type:

type Clock interface {
	Now() time.Time
}

Next is the meat of your application. Overall, it needs to be able to handle the following responsibilities regardless of which API you use:

These responsibilities map to the following interface (comments differ from the actual documentation):

type Application interface {
	// Determines whether the application owns an IRI
	Owns(c context.Context, id *url.URL) bool
	// Gets ActivityStream content
	Get(c context.Context, id *url.URL, rw RWType) (PubObject, error)
	GetAsVerifiedUser(c context.Context, id, authdUser *url.URL, rw RWType) (PubObject, error)
	// Determines if it has ActivityStream data at the IRI
	Has(c context.Context, id *url.URL) (bool, error)
	// Setting ActivityStream data
	Set(c context.Context, o PubObject) error
	// Getting an actor's outbox or inbox
	GetInbox(c context.Context, r *http.Request, rw RWType) (vocab.OrderedCollectionType, error)
	GetOutbox(c context.Context, r *http.Request, rw RWType) (vocab.OrderedCollectionType, error)
	// Creating new ids
	NewId(c context.Context, t Typer) *url.URL
	// Obtaining the public key for another user for verification purposes
	GetPublicKey(c context.Context, publicKeyId string) (pubKey crypto.PublicKey, algo httpsig.Algorithm, user *url.URL, err error)
	// Whether adding/removing is permitted
	CanAdd(c context.Context, o vocab.ObjectType, t vocab.ObjectType) bool
	CanRemove(c context.Context, o vocab.ObjectType, t vocab.ObjectType) bool
}

Whew! That's the bulk of the work in writing an ActivityPub application right there. Once implemented, your application will be able to handle the basic behaviors for common activity types as outlined in the ActivityPub specification.

Those of you that are observant may have noticed that there are a few other types referenced:

Now that the hard part is over, you optionally can implement another interface: the Callbacker. You will need one for either the SocialAPI or FederateAPI, but if you choose to implement both you will need two.

The Callbacker is where the real customization for your application can take place. Whenever one of its supported activity types is sent to your server, these callback functions will be invoked. Since the basic behaviors are already handled for you, you can focus on the custom behavior. For example, a Create will already properly call your application's functions as needed, so in the Callbacker you could have it detect if it was trying to create an Article and just have it automatically syndicate it to a proprietary social network, for example. The precise default behavior provided for you will be explored in depth in the API specific sections. The Callbacker looks like:

type Callbacker interface {
	Create(c context.Context, s *streams.Create) error
	Update(c context.Context, s *streams.Update) error
	Delete(c context.Context, s *streams.Delete) error
	Add(c context.Context, s *streams.Add) error
	Remove(c context.Context, s *streams.Remove) error
	Like(c context.Context, s *streams.Like) error
	Block(c context.Context, s *streams.Block) error
	Follow(c context.Context, s *streams.Follow) error
	Undo(c context.Context, s *streams.Undo) error
	Accept(c context.Context, s *streams.Accept) error
	Reject(c context.Context, s *streams.Reject) error
}

A default implementation that does nothing for all callbacks is perfectly valid since the ActivityPub required behavior is handled for you by default.

Now that these interfaces are implemented, you can create a Pubber type. What does this do for you? Why use a funky naming thing? Why am I talking to myself? Well, it handles HTTP requests to an actor's inbox or outbox and carries out the ActivityPub-specific behavior required for the API(s) you wish to support. This is one of the two big behaviors mentioned at the beginning of this section. To get a Pubber requires calling a constructor. The exact constructor you call depends on which API(s) you wish to support, and so this will be addressed in the sections below. For now, let's assume you've already done that and have a shiny new Pubber ready to get going.

What can you do with a Pubber? Well, it's API is straightforward:

type Pubber interface {
	// Handle a HTTP request to an actor's inbox
	PostInbox(c context.Context, w http.ResponseWriter, r *http.Request) (bool, error)
	GetInbox(c context.Context, w http.ResponseWriter, r *http.Request) (bool, error)
	// Handle a HTTP request to an actor's outbox
	PostOutbox(c context.Context, w http.ResponseWriter, r *http.Request) (bool, error)
	GetOutbox(c context.Context, w http.ResponseWriter, r *http.Request) (bool, error)
}

So whatever handler you have to handle an actor's inbox or outbox would look similar to this:

// Given:
//     var myPubber pub.Pubber
var outboxHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
	c := context.Background()
	// Populate c with application specific information here
	// ...
	if handled, err := myPubber.PostOutbox(c, w, r); err != nil {
	  // Write to w
	} else if handled {
	  return
	}
	if handled, err := myPubber.GetOutbox(c, w, r); err != nil {
	  // Write to w
	} else if handled {
	  return
	}
	// Handle non-ActivityPub request, such as responding with a HTML
	// representation with correct view permissions.
}
var inboxHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
	c := context.Background()
	// Populate c with application specific information here
	// ...
	if handled, err := myPubber.PostInbox(c, w, r); err != nil {
	  // Write to w
	} else if handled {
	  return
	}
	if handled, err := myPubber.GetInbox(c, w, r); err != nil {
	  // Write to w
	} else if handled {
	  return
	}
	// Handle non-ActivityPub request, such as responding with a HTML
	// representation with correct view permissions.
}

That handles one of the two big behaviors! The other behavior is to handle serving the raw ActivityStream data at the URL endpoint that matches its id property. There are two ways to do this: one function provides default HTTP Signature checking, and the other lets you use your own custom authentication/authorization method.

// Given:
//     var myApp pub.Application
//     var myClock pub.Clock
//     var myVerifier pub.SocialAPIVerifier - This type to be discussed in a later section
var myPubHandler pub.HandlerFunc = pub.ServeActivityPubObject(myApp, myClock)
// Alternatively:
myVerifierFactory = func(c context.Context) pub.SocialAPIVerifier {
	// ...
	return myVerifier
}
var myCustomVerifiedPubHandler pub.HandlerFunc = pub.ServeActivityPubObjectWithVerificationMethod(myApp, myClock, myVerifierFactory)
// Serve ActivityStreams objects:
var serveIRIHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
	c := context.Background()
	// Populate c with application specific information here
	// ...
	if handled, err := myPubHandler(c, w, r) /* or myCustomVerifiedPubHandler */; err != nil {
	  	  // Write to w
	} else if handled {
	  return
	}
	// Handle non-ActivityPub request, such as responding with a HTML
	// representation with correct view permissions.
}

And that's about all it takes to hook ActivityPub behaviors into your new or existing application!

Now that you have the basics, it is time to explore the specific requirements and specific default behaviors for each of the APIs in ActivityPub. As well as how to create the Pubber type.

ActivityPub: SocialAPI (Client to Server)

The SocialAPI provides the following default behaviors for the following activities (heavily summarized):

Now, to obtain a Pubber you will need to call:

func NewSocialPubber(
	clock Clock,
	app SocialApplication,
	cb Callbacker) Pubber

You should have the Clock and Callbacker from the previous section ActivityPub: The Two Protocols. However, the SocialApplication is defined as:

type SocialApplication interface {
	Application
	SocialAPI
}

And since you already have an Application you just have to implement the methods in SocialAPI, which provide the following necessary behaviors for this library:

This is how it looks as an interface (comments differ from the source):

type SocialAPI interface {
	// Determine an actor's id
	ActorIRI(c context.Context, r *http.Request) (*url.URL, error)
	// Optional authentication/authorization scheme
	GetSocialAPIVerifier(c context.Context) SocialAPIVerifier
	// Gets an actor's public key for verifying incoming messages
	GetPublicKeyForOutbox(c context.Context, publicKeyId string, boxIRI *url.URL) (crypto.PublicKey, httpsig.Algorithm, error)
}

This introduces us to our final interface: SocialAPIVerifier. It is only necessary if you want to implement OAuth 2.0 or some other authentication/authorization scheme besides the default HTTP Signatures. Its interface is simple but nuanced:

type SocialAPIVerifier interface {
	Verify(r *http.Request) (authenticatedUser *url.URL, authn, authz bool, err error)
	VerifyForOutbox(r *http.Request, outbox *url.URL) (authn, authz bool, err error)
}

That's it! With your knowledge from this section as well as the ActivityPub: The Two Protocols section, you are good to build an application using ActivityPub's SocialAPI protocol.

ActivityPub: FederateAPI (Server to Server)

The FederateAPI provides the following default behaviors for the following activities (heavily summarized):

This will let you federate with peer servers, but without the SocialAPI your application will presumably need some other mechanism for users to create content. Once created, this library will take care of federating with peers.

It also solves the "ghost replies" problem for you. This is when Alex publicly replies to a message from Addison, but the message is not forwarded to Addison's followers. If Addison then replies again, her followers would see Addison's original message and reply, but not Alex's reply in the middle. This library solves that problem for you by having Addison's server forward Alex's message to Addison's followers.

To obtain a Pubber you will need to call:

func NewFederatingPubber(
	clock Clock,
	app FederateApplication,
	cb Callbacker,
	d Deliverer,
	client HttpClient,
	userAgent string,
	maxDeliveryDepth, maxForwardingDepth int) Pubber

You should have the Clock and Callbacker from the previous section ActivityPub: The Two Protocols. It also requires a FederateApplication, Deliverer, HttpClient, userAgent, maxDeliveryDepth, and maxForwardingDepth. The FederateApplication is defined as:

type FederateApplication interface {
	Application
	FederateAPI
}

And since you already have an Application you just have to implement the methods in FederateAPI, which provide the following necessary behaviors for this library:

This is how it looks as an interface (comments differ from the source):

type FederateAPI interface {
	// How to handle follow requests (auto-approve, auto-reject, manually decide)
	OnFollow(c context.Context, s *streams.Follow) FollowResponse
	// Whether an interaction should be blocked
	Unblocked(c context.Context, actorIRIs []*url.URL) error
	// Filter recipients when forwarding messages in order to avoid "ghost replies"
	FilterForwarding(c context.Context, activity vocab.ActivityType, iris []*url.URL) ([]*url.URL, error)
	// Sign federated messages with HTTP Signatures
	NewSigner() (httpsig.Signer, error)
	// Obtain private and public keys for proper signing
	PrivateKey(boxIRI *url.URL) (privKey crypto.PrivateKey, pubKeyId string, err error)
}

Next is the Deliverer. Since this library is sending HTTP requests on your application's behalf, there needs to be some way to handle retries, backing off, and persistent state if a request needs to be retried after downtime. The Deliverer implementation you write can be as fancy as you wish:

type Deliverer interface {
	Do(b []byte, to *url.URL, toDo func(b []byte, u *url.URL) error)
}

The naive implementation is simply:

type NaiveDeliverer struct {}

func (d *NaiveDeliverer) Do(b []byte, to *url.URL, toDo func(b []byte, u *url.URL) error) {
	_ = toDo(b, to)
}

The final new type is the HttpClient. Since this library is sending HTTP requests on your application's behalf, you remain in full control of the actual HTTP delivery mechanism. It should be no surprise that http.Client implements this interface:

type HttpClient interface {
	Do(req *http.Request) (*http.Response, error)
}

The final options in the call to NewFederatingPubber are settings used to govern details in the behavior of your application:

And with that, you have your Pubber and a way to federate! With this section as well as the ActivityPub: The Two Protocols section, you can build an application using ActivityPub's FederateAPI protocol.

ActivityPub: Both APIs (Client to Server & Server to Server)

Surprise, you can implement both the SocialAPI and FederateAPI portions of ActivityPub using this library! Unsurprisingly, the way to create a Pubber in this case is both of the NewSocialPubber and NewFederatingPubber functions squished into one. Squish is a highly technical term:

func NewPubber(
	clock Clock,
	app SocialFederateApplication,
	client, server Callbacker,
	d Deliverer,
	httpClient HttpClient,
	userAgent string,
	maxDeliveryDepth, maxForwardingDepth int) Pubber

The SocialFederateApplication type is simply:

type SocialFederateApplication interface {
	Application
	SocialAPI
	FederateAPI
}

Follow the guidance in the other two sections to know more about these parameters and their types:

References

These are the references I used or referred to when building all libraries within the go-fed organization, including but not limited to the go-fed/activity library.

W3C Specifications:

RFCs:

Other documents and links: