← Seminar notes
class: title, smokescreen, shelf, no-footer background-image: url("/media/c3f3e92ea98440110e247dcb4543c3f98be104312a57aa8738d89de9a39e0259/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: roomy # The Fediverse .center[] --- class: compact # Fundamental Networks Topology .center[] --- class: col-2 .center[] # The IndieWeb ### A people-focused alternative to the "corporate web". <br><br> #### ● Your content is yours; #### ● You are better connected; #### ● You are in Control. --- class: roomy #  True to the Unix-philosophy of small programs to do a small job, GNU social is a social communication software used in <a href="https://blog.diogo.site/what-is-the-fediverse/">federated social networks</a>. It is widely supported and has a large userbase. It is already used by the <a href="https://www.fsf.org/">Free Software Foundation</a>, and <a href="https://gnusocial.no/rms">Richard Stallman</a> himself. --- class: img-right .center[] # activity streams - Users are kept up-to-date with their subscription activities - Used when you want to prompt your users to respond to other user’s actions - Used when you want to expose and promote the functionality of your system by guiding people by the actions of others. - Do not use when your system does not have user activity as one of its key elements. --- class: roomy # The actor model .center[] --- class: compact ## What's the difference between URI and URLs In GNU social a user has the right of changing its nickname and, therefore, sometimes this happens: .center[https://loadaverage.org/up201705417 → https://loadaverage.org/diogo] On the other hand, my id doesn't change, so: .center[https://loadaverage.org/index.php/user/386167] .center[↓] .center[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 ## Briefing on ActivityPub .center[] --- class: col-2 .center[] # OStatus Overview ### OStatus combines a set of different protocols. <br><br> #### ● ActivityStreams; #### ● PubSubHubbub; #### ● Salmon; #### ● Webfinger. --- ## 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... --- {"@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 :) --- class: roomy # Activity Vocabulary The vocabulary is defined in two parts: - Core set - Extended set --- class: compact, col-2 # Activity Streams 2.0 ## Core Types - Object - Link - Activity - Instransitive Activity - Collection - OrderedCollection - CollectionPage - OrderedCollectionPage <br> ## Activity Types - Accept - Announce - Create - Delete - Follow - Like - Dislike - Note - Reject - ... this goes on and on ... --- 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 ## 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: roomy # Using the Fediverse - There isn't a single culture - People have multiple interests - Focus on Your Self --- 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 - Global static array of events ([protected static $_handlers = [\];](https://notabug.org/diogo/gnu-social/src/nightly/lib/event.php#L52)) - 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? :)
This slide viewer uses JavaScript.
Read the seminar notes
.