class: title, smokescreen, shelf, no-footer background-image: url("images/cover-slide-background.png") # A Tour of ActivityPub ### Lecturer: <a href="https://diogo.site/">Diogo Cordeiro</a><br>GNU social co-maintainer and author of the AP plugin --- layout: true .footer[ - [Diogo Cordeiro](https://diogo.site/) - Hackers at Porto · Talks@DCC · FCUP, Universidade do Porto ] --- class: img-left-full  # A brief historical context The fediverse, a portmanteau of "federation" and "universe", is part of the IndieWeb and consists on a decentralized social network that started with GNU social. Nowadays there are various pieces of software, other than GNU social, in the fediverse, like Pleroma and Mastodon, (all of which can be installed on your own server). --- class: col-2, compact .center[] <br><br><br> Centralised Network -Belongs to a single central power point (a host). Connected to all satellite nodes. Decentralised Network – Belongs to many different hosts. Each with their own satellite nodes. Distributed Network – Contains neither a host or satellite nodes. They just contain these self determining end point systems which connect with any node they want. --- class: compact, col-2 ## Actor Types Actor types are Object types that are capable of performing activities. .center[] - Application - Describes a software application (Bots) - Group - Represents a formal or informal collective of Actors (some federation controversy) - Organization - Represents an organization - Person - Represents an individual person (User) - Service - Represents a service of any kind (Tools, used Protocol,...) --- class: compact ## What's the difference between URI and URLs Not all URIs are URLs because a URI could be a name instead of a locator (that happens a lot on OStatus). In theory, a URL can be trusted to be an URI but, that's not always the case. Common example: In GNU social a user has the right of changing its nickname, therefore, https://loadaverage.org/up201705417 -> https://loadaverage.org/diogo (this happened) On the other hand, my id is the same, so: https://loadaverage.org/index.php/user/386167 -> https://loadaverage.org/index.php/user/386167 It's important to keep in mind that we can only trust an URL if this is claimed to be an URI. --- class: roomy # Decentralization in the fediverse context When local actors (actors in the same node) participate in discussions with external actors (from other nodes) or simply subscribe to external actors' content, their node push that content to the relevant other nodes and eventually makes it available to all the connected actors. That selective node-based distribution creates, inside the global network, a more or less extended local aggregation that is called the fediverse. --- class: compact ## Forwarding from Inbox Alyssa makes a post about her having successfully presented a paper at a conference and sends it to her followers collection, which includes her friend Ben. Ben replies to Alyssa's message congratulating her and includes her followers collection on the recipients. However, Ben has no access to see the members of Alyssa's followers collection, so his server does not forward his messages to their inbox. Without the following mechanism, if Alyssa were then to reply to Ben, her followers would see Alyssa replying to Ben without having ever seen Ben interacting. This would be very confusing! --- class: col-2  # The IndieWeb ### A people-focused alternative to the "corporate web". - When you post something on the web, it should belong to you, not a corporation; - Your articles and status messages can go to all services, not just one, allowing you to engage with everyone; - You can post anything you want, in any format you want, with no one monitoring you. --- # Activity Streams 2.0 Little "a" activity streams : are a UI paradigm for displaying recent activity within a context. Activities are typically displayed in reverse chronological order and consist of relatively simple statements such as "John uploaded a new photo" or "12 people liked Sally's post". Big "A" Activity Streams : is a data format for encoding and transferring activity/event metadata. The first version of the specification was published in 2011 by the independent Activity Streams Working Group and is based on extending Atom. The current (2.0+) version of the spec is JSON-based. --- ## Thoughts on JSON-ld <blockquote> <p>JSON will always be second to XML in awesomeness anywayz...</p> <footer>-- <cite>Mikael Nordfeldth</cite></footer> </blockquote> and <blockquote> <p>My brain hurts when they try to do in json (JSON-LD) what has existed and worked in XML for ages.</p> <footer>-- <cite>Mikael Nordfeldth</cite></footer> </blockquote> There was a small war and resistance on GNU social's side about the adoption of JSON. But they can play nice together. Our strategy was to keep XML for data definitions and to use JSON for data interchange. --- class: col-3, compact .center[] # About GNU social GNU social is a free social networking platform. It helps people in a community, company or group to exchange short status updates, do polls, announce events, or other social activities (and you can add more!). Users can choose which people to "follow" and receive only their friends' or colleagues' status messages. It provides a similar service to proprietary social network sites, but is much more awesome. With a little work, status messages can be sent to mobile phones, instant messenger programs (using XMPP), and specially-designed desktop clients that support the Twitter API. GNU social supports an open standard called OStatus https://www.w3.org/community/ostatus/ that lets users in different networks follow each other. It enables a distributed social network spread all across the Web. GNU social was originally developed as "StatusNet" by StatusNet, Inc. with Evan Prodromou as lead developer. --- # Before moving forward... ## ...where did we came from? --- class: col-2, compact .center[] <br><br> # OStatus Overview OStatus combines in a natural and efficient way a set of different pieces allowing decentralized communications in the web: - ActivityStreams encodes the notices, activities and social events (originally 1.0, therefore in Atom or RSS) - PubSubHubbub sends, in real time, these feeds to its subscribers around the web; - Salmon notifies its users about replies to their posts; - WebFinger makes it easy to find other users in the fediverse. --- ## What happens when we subscribe to a user? ### .center[] In this dialog we insert our URI in the fediverse, a request from this instance to ours' WebFinger endpoint will be received. Then we will use PubSubHubbub to subscribe the feed in real time (we won't have to regularly check for updates). --- class: col-2, compact # The Free Network .center[] <br><br> ## Some general statistics Nodes: 4400+ Users: 2,930,700+ Softwares: 30+ Protocols: 14 Statuses: 267,740,000+ ### GNU social Nodes: 150+ Users: 3400+ Statuses: 830,000+ Something wrong isn't right... --- class: compact # If all those protocols allow the same why is ActivityPub being so widely adopted instead of the already existing ones? ## It was essentially a matter of presentation. ActivityPub does solve some technical problems of the already existing alternatives. But, despite its problems and trade-offs (because it isn't perfect either), "ActivityPub is to HTTP what HTTP was to TCP/IP" (in the words of Riley Taylor) whereas older protocols, like OStatus, were shared as a sophisticated (and overkill) solution to build a federated Twitter clone. This fact together with the simplicity claimed to be part of ActivityPub implementation lead to its large and fast adoption. --- {"@context": "https://www.w3.org/ns/activitystreams", "type": "Person", "id": "https://loadaverage.org/user/index.php/386167", "name": "Diogo Cordeiro", "preferredUsername": "diogo", "summary": "PHP enthusiast hailing from FCUP", "inbox": "https://loadaverage.org/diogo/inbox/", "outbox": "https://loadaverage.org/diogo/outbox/", "followers": "https://loadaverage.org/diogo/followers/", "following": "https://loadaverage.org/diogo/following/", "liked": "https://loadaverage.org/diogo/liked/"} # A Tour of ActivityStreams ### Before delving into the fediverse and ActivityPub :) --- # Activity Vocabulary The vocabulary is defined in two parts: - A Core set properties describing the generalized structure of an Actvity; and - An Extended set of properties that cover specific types of Activities and Artifacts common to many social Web application systems. --- class: compact # Core Types - Object - A simble, non-specific object - Link - Activity - Subtype of Object that describes some form of action - Instransitive Activity - Subtype of Activity representing intransitive actions - Collection - Subtype of Object that represents ordered or unordered sets of Object or Link instances - OrderedCollection - Subtype of Collection in which members of the logical collection are assumed to always be strictly ordered - CollectionPage - Used to represent distinct subsets of items from a Collection - OrderedCollectionPage - Used to represent ordered subsets of items from an OrderedCollection --- class: compact ## Activity { "@context": "https://www.w3.org/ns/activitystreams", "type": "Activity", "summary": "Sally did something to a note", "actor": { "type": "Person", "name": "Sally" }, "object": { "type": "Note", "name": "A Note" } } --- class: compact ## IntransitiveActivity { "@context": "https://www.w3.org/ns/activitystreams", "type": "Travel", "summary": "Sally went to work", "actor": { "type": "Person", "name": "Sally" }, "target": { "type": "Place", "name": "Work" } } --- class: compact ## OrderedCollection { "@context": "https://www.w3.org/ns/activitystreams", "summary": "Sally's notes", "type": "OrderedCollection", "totalItems": 2, "orderedItems": [ { "type": "Note", "name": "A Simple Note" }, { "type": "Note", "name": "Another Simple Note" } ] } --- class: compact # Activity Types - Accept - Indicates that the `actor` accepts or acknowledges the `object` - Announce - Typically a note repost (indicates that the `actor` is calling the `target`'s attention to the `object`) - Create - Indicates that the `actor` has created the `object` - Delete - Indicates that the `actor` has deleted the `object` - Follow - Indicates that the `actor` subscribed the `object` - Like - Indicates that the `actor` likes, recommends or endorses the `object` - Dislike - Indicates that the `actor` dislikes the `object` (DON'T CONFUSE WITH UNDO LIKE) - Note - Represents a short written work typically less than a single paragraph in length - Reject - Indicates that the actor is rejecting the object - ... this goes on and on ... --- class: compact ## Accept { "@context": "https://www.w3.org/ns/activitystreams", "summary": "Sally accepted an invitation to a party", "type": "Accept", "actor": { "type": "Person", "name": "Sally" }, "object": { "type": "Invite", "actor": "http://john.example.org", "object": { "type": "Event", "name": "Going-Away Party for Jim" } } } --- [.center[]](https://www.restapitutorial.com/httpstatuscodes.html) --- class: compact # Using the Fediverse ## There isn't a single culture Of course, despite your best or worst efforts, you wouldn’t be able to do more than skew the culture of your part of the Fediverse: there’s no unified culture to the Fediverse, due to the distributed way in which instances network. A lot of people come to the Fediverse because they’ve become untenably frustrated with their experience on legacy social media, like Twitter or Facebook. Moving to the Fediverse is a really great solution to that frustration! Some platforms, like Friendica, are similar to Facebook. Others, like GNU social, are like Twitter. So before you post, think about the problems you remember from the platforms you came from, and give some thought to if you might be recreating the culture that led to those problems here. --- ### People have multiple interests Some pieces of software like mastodon don't support groups, this lead to a huge number of mastodon instances that are interest-strict, there is a technology-only instance, music-only, etc. We don't find this ideal as it limits the variety of posts in the user's feed. Furthermore, it leads the user to create different accounts in different instances, which defies the whole purpose of federation. GNU social's solution for this issue was to implements groups. Groups are not _that_ different of hashtags other than in concept and usage. You can subscribe to a !group but not to an #hashtag. Hashtags are idealized for momentaneous episodes while groups are more of a long-term thing. --- ## Focus on Your Self The culture that will have the strongest effect on your sense of identity is the one you build inside your head, and that’s determined mostly by what you’re exposed to. Make the culture of the Fediverse something you want to be exposed to - follow people who will bring you to that goal, boost and write posts that fulfill that goal, and disregard the rest. --- class: compact, col-2 # Implementing ActivityPub - Architecture ActivityPub enables a decentralized social web, where a network of servers interact with each other on behalf of individual users/clients, very much like email operates at a macro level. On an ActivityPub compliant server, individual user accounts have an inbox and an outbox that accept HTTP GET and POST requests via API endpoints. They usually live somewhere like https://example.social/users/diogo/inbox and https://example.social/users/diogo/outbox, but they can really be anywhere as long as they are at a valid URI. Individual users are represented by an Actor object, which is just a JSON-LD file that gives information like username and where the inbox and outbox are located so you can talk to the Actor. Every message sent on behalf of an Actor has the link to the Actor’s JSON-LD file so anyone receiving the message can look up all the relevant information and start interacting with them. --- class: compact, col-2 # Implementing ActivityPub - Actor .center[] Actor is the user doing a change in the fediverse and are identified by an URI. In GNU social they have the following format: `https://myinstance.net/user/{id}`. Thus allowing users to change their nicknames. --- class: compact, col-2 # Implementing ActivityPub - Objects .center[] <br><br><br> Objects are our building blocks for the bigger concept of Activity. - Actors are defined by the profile object - Error - Tag - Mention Tag - Notice - Attachment --- class: compact, col-2 # Implementing ActivityPub - Activities .center[] <br><br><br><br><br> Wrappers allow to give additional information on a given object. The implemented wrappers are the following: - Create - Announce - Delete - Undo - Accept - Reject - Like - Follow --- class: compact # Event-driven programming - Software Engineering This is a dominant paradigm used in graphical user interfaces and other applications (e.g.: JavaScript web applications) that are centered on performing certain actions in response to user input. These are thus essentially made of: - A global static array of events (protected static $_handlers = [];) - Event handler - addHandler - Handle a certain event - handle - let other code know about a certain event and further develop on it - hasHandler - Check to see if an event handler exists - getHandlers - Get an handler for a specifc event - clearHandlers - Disables any and all handlers that have been set up so far (only use if you know what you're doing) --- class: compact # Event-driven programming - Software Engineering ## addHandler To run some code at a particular point in GNU social processing. Named events include receiving an XMPP message, adding a new notice, or showing part of an HTML page. The arguments to the handler vary by the event. Handlers can return two possible values: false means that the event has been replaced by the handler completely, and no default processing should be done. Non-false means successful handling, and that the default processing should succeed. (Note that this only makes sense for some events.) Handlers can also abort processing by throwing an exception; these will be caught by the closest code and displayed as errors. source: https://notabug.org/diogo/gnu-social/src/nightly/lib/event.php#L54-L82 --- class: compact # Event-driven programming - Software Engineering ## handle Events are any point in the code that we want to expose for third-party developers to use. We pass in an array of arguments (including references, for stuff that can be changed), and each assigned handler gets run with those arguments. Exceptions can be thrown to indicate an error. source: https://notabug.org/diogo/gnu-social/src/nightly/lib/event.php#L84-L112 --- class: compact # Event-driven programming - Software Engineering ## In GNU social class Plugin { function __construct() { Event::addHandler('InitializePlugin', array($this, 'initialize')); Event::addHandler('CleanupPlugin', array($this, 'cleanup')); foreach (get_class_methods($this) as $method) { if (mb_substr($method, 0, 2) == 'on') { Event::addHandler(mb_substr($method, 2), array($this, $method)); } } $this->setupGettext(); } // Lots of code } --- class: compact # Event-driven programming - Software Engineering ## In GNU social's Nodeinfo plugin public function getProtocols() { $protocols = []; // Let other code handle this Event::handle('NodeInfoProtocols', [&$protocols]); } --- class: compact # Event-driven programming - Software Engineering ## In GNU social's ActivityPub plugin // Catch Nodeinfo's protocols info event public function onNodeInfoProtocols(array &$protocols) { $protocols[] = "activitypub"; // Let other plugins (like OStatus) add their info too return true; } --- class: compact, col-2 # Implementing ActivityPub - Exploring the Network .center[] We have to grab remote users and create an identity for them in the local instance. - Find remote profiles from mentions in texts (onEndFindMentions) - Allow to reference a remote profile (onStartCommandGetProfile) - URI from profile - Allow to reference a remote profile (onStartGetProfileUri) - profile from URI --- class: img-left, compact  # WebFinger - extractWebFingerIds - WebFinger matches: @user@example.com or even @user--one.george_orwell@1984.biz - extractUrlMentions - Profile URL matches: @example.com/mublog/user These two are called on onEndFindMentions (Find any explicit remote mentions) --- class: compact { "subject": "acct:diogo@pixelfed.social", "aliases": [ "https://pixelfed.social/diogo", "https://pixelfed.social/users/diogo" ], "links": [ { "rel": "http://webfinger.net/rel/profile-page", "type": "text/html", "href": "https://pixelfed.social/diogo" }, { "rel": "self", "type": "application/activity+json", "href": "https://pixelfed.social/users/diogo" } ] } --- class: compact public static function pull_remote_profile($arg) { if (preg_match('!^((?:\w+\.)*\w+@(?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+)$!', $arg)) // WebFinger lookup try { return Activitypub_profile::ensure_web_finger($arg); } catch (Exception $e) { common_log(LOG_ERR, "WebFinger lookup failed for $arg : $e->getMessage()"); } // Look for profile URLs, with or without scheme (more regex stuff ...) foreach ($urls as $url) try { return Activitypub_profile::fromUri($url); } catch (Exception $e) { common_log(LOG_ERR, 'Profile lookup failed for ' . $arg . ': ' . $e->getMessage()); } return null; } --- class: compact public static function ensure_web_finger($addr) { // Normalize $addr, i.e. add 'acct:' if missing $addr = Discovery::normalize($addr); // Try the cache $uri = self::cacheGet(sprintf('activitypub_profile:webfinger:%s', $addr)); if ($uri !== false) { if (is_null($uri)) // Negative cache entry // TRANS: Exception. throw new Exception(_m('Not a valid webfinger address (via cache).')); try { return self::fromUri($uri); } catch (Exception $e) { common_log(LOG_ERR, sprintf(__METHOD__ . ': Webfinger address ' . 'cache inconsistent with database, did not find ' . ' Activitypub_profile uri==%s', $uri)); self::cacheSet(sprintf('activitypub_profile:webfinger:%s', $addr), false); } } --- class: compact // Now, try some discovery $disco = new Discovery(); try { $xrd = $disco->lookup($addr); } catch (Exception $e) { // Save negative cache entry so we don't waste time looking it up again. // @todo FIXME: Distinguish temporary failures? self::cacheSet(sprintf('activitypub_profile:webfinger:%s', $addr), null); // TRANS: Exception. throw new Exception(_m('Not a valid webfinger address.')); } $hints = array_merge(['webfinger' => $addr], DiscoveryHints::fromXRD($xrd)); // If there's an Hcard, let's grab its info // If we got a profile page, try that! // XXX: try hcard // XXX: try FOAF // TRANS: Exception. %s is a webfinger address. throw new Exception(sprintf(_m('Could not find a valid profile for "%s".'), $addr)); } --- class: img-left, compact # Implementing ActivityPub - Delivering .center[] We've also got to publish our activies (thus the name ActivityPub) to remote instances. - Notify remote instance of subscription (onEndSubscribe) - Notify remote instance when a subscription is canceled (onEndUnsubscribe) - Notify remote instances when a notice is liked (onEndFavorNotice) --- class: img-left, compact # Implementing ActivityPub - Delivering .center[] We've also got to publish our activies (thus the name ActivityPub) to remote instances. - Notify remote instances when a notice like is reverted (onEndDisfavorNotice) - Notify remote instances when a notice is deleted (onEndDeleteOwnNotice) - Notify remote instances when a notice is created (onStartNoticeDistribute) --- class: compact, col-2 # Hold on, if you have a Postman then there sure is an inbox, right? Two actually! :) There are many possible analogies between the Fediverse and the real world. One of those is that people, sometimes, share the same "building". A list of some public GNU social instances is available here. Well, if two people live in the same place then our Postman doesn't have to "travel" twice! Thus the concept of shared inbox. Lets start with the inbox. An inbox is where we, I mean, the Postman publishes our Activity. By now you should have already realized that Activities are just verbs wrapping objects thus basically forming a phrase: `Actor` `verb` `Object` `to Attention Actor(s)`. Well, for each `Attention actor` we deliver the same `Actor` `verb` `Object`. Since it is the same message and frequently for people residing in the same server we can obviously reduce a lot of traffic and processing power here. The Explorer hands the Postman the sharedInbox address always that it is possible. --- class: compact, col-2 # On ActivityPub collections Some key collections are: - Followers - Following - Liked - Outbox Collections are lists, and lists are very important in the world of fediverse both for Server to Server and Client to Server. <br><br> ## Why? When an instance posts an activity to our instance we must ensure it really comes from its actor. Options are: - Use a form of authentication, usually: - S2S - HTTP signatures - C2S - OAuth 2 - If no authentication is used, visit Actor's collections to ensure that he really did the post. --- class: compact # Final remarks If you’d like to know more about ActivityPub, you can of course <a href="https://www.w3.org/TR/activitypub/" target="_blank">read the ActivityPub spec</a>. It’s important to know that while the ActivityPub spec lays out how messages are sent and received, the different types of messages are specified in <a href="https://www.w3.org/TR/activitystreams-core/" target="_blank">the Activity Streams 2.0 spec</a>, and the actual formatting of the messages that are sent is specified in the <a href="https://www.w3.org/TR/activitystreams-vocabulary/" target="_blank">Activity Streams Vocabulary</a> spec. It’s important to familiarize yourself with all three.</p> <p>You can join the <a href="https://www.w3.org/wiki/SocialCG" target="_blank">Social Web Incubator Community Group</a>, a W3C Community Group, to participate in discussions around ActivityPub and other social web tech standards. They have monthly meetings that you can dial into that are listed on the wiki page.</p> --- class: roomy # Questions? :)
Read the seminar notes