Liberating clients from servers, without throwing out baby with bathwater

I’ve been thinking about C2S for the last few months and have started my own (experimental) implementation. It’s not anywhere close yet given I don’t work particularly fast (nor is anyone paying me to!), but I feel like the overall architecture I’ve thought up (see README) may bring up a few ideas: KittyShopper/ois: experimental, unfinished, likely never will be finished, "dumb" activitypub c2s server - Codeberg.org

In particular, my goal is to keep user experience comparable with an “all in one” implementation while keeping the benefits of C2S, and federating with the current network as is. And I can’t see how that can’t be done if you’re OK with running parts of your client in a server (which I propose is necessary to have clients people would actually want to use)

Awesome! Another one to add to your list @aschrijver .

Why do you say think so @kopper ? Have you had a look at how your thinking on the division of labour between server and client compares with how @stevebate is doing it in Flowz?

2 Likes

Have you had a look at how your thinking on the division of labour between server and client compares with how @stevebate is doing it in Flowz?

I admittedly haven’t looked into Flowz so I can’t comment on it specifically. My comment there was mainly about things like timelines and reply trees, especially doing them fast enough to be as snappy as, say, Mastodon.

Say, if you need to do timelines on the client, you’ll need to re-load your inbox and filter every activity which isn’t relevant (follows, likes to other people’s posts, someone messing around with their collections) to extract Creates and Announces (and whatever else you’re interested in). This can be done fully client-side if you try, but it would be slow enough to impact UX. Doing this once, on activity ingestion time (which implicitly requires a consistently online server to handle activities any time they may come in) and serving a pre-filtered timeline to the client as-is (potentially just as object IDs to be loaded by the client later on) would be more efficient.

Reply trees are similar, but the problem there isn’t so much the computational overhead but “request waterfalls” as you load each individual reply collection to fetch nested replies (or parent posts through walking inReplyTo chains). The context collection can partially solve this, but loading the entire collection to just get the new replies (or ones in the middle, so you can’t iterate it backwards) would be just as slow. A server could pre-compute a tree ahead of time and serve the entire tree as-is in a single response (bar pagination).

This part assumes all remote instances offer reply collections, which they don’t. Pleroma/Akkoma I believe are the largest implementations not supporting them. A server could compute reply trees by matching inReplyTo in incoming Creates, allowing them to be more complete. If multi-user, it could also index replies by people other people in your “client instance” follow, making the impact of missing reply collections less of a problem.

Additionally, “client instance” administrators can moderate the posts indexed by their clients, which I expect will be the primary avenue where remote content moderation happens.

A server would also be required to allow functionality that needs fast response times, like non-locked accounts (that automatically Accept their Follows) or things like GoToSocial’s interaction approvals (assuming you don’t intend to manually review every Like that comes your way).

There’s also a data size aspect to this. Unlimited mobile data still isn’t a thing in parts of the world (including where I live). Even if you could tolerate the computational overhead or the waiting involved with offloading these to a client, you’d still need to download all the data you discard, which directly translates to how expensive it is to participate in the network. In addition to the pre-filtering, a server could use a specialized API that offers more compact, possibly non-standard, representations of data for extremely low-bandwidth uses.

As far as I’m aware, the only way to not run these on a server-hosted client is to bake them into the C2S server itself, which severely bloats the scope of the C2S server, and reduces client flexibility as they now have to work under the constraints imposed upon them by the C2S server, creating essentially a “ActivityPub flavored Mastodon API” situation.

Part of the experimentation I’m doing with Flowz is exploring how much can be done with a standard C2S client. I had always assumed that client-side timeline building would be too slow, but so far that hasn’t been my experience. The client typically only needs to incrementally process the inbox in small chunks (pages) rather than loading the entire inbox.

However, I think it would be good to support server-side timeline building too. The timelines are just custom OrderedCollections. There’s already a streams property for actors that could be used to discover what timelines a specific server implementation supports. An FEP could define how to identify a timeline (home, local, federated, custom filtered, whatever) so a client can know how to access and use the related collections. This is still standard C2S, but it requires specific server functionality that not all servers will support. If the client doesn’t detect the server-generated well-known timeline collections, it could implement the option to fallback to client-side timeline generation for certain timelines like the home and profile/outbox timelines.

1 Like

it can be done that way, and that’s probably the most feasible way to do it. i’m not sure it’s “necessary”, but it is very convenient to have your client run on a server as a web-app. in the xmpp world, you can look at Movim as an example of this model in action.

this is a matter of perspective. what does the timeline contain? the thing about activitypub and activitystreams is that they were designed around the idea of “activity streams”, which is a design pattern that aggregates and exposes a stream of activities. see Activity Stream design pattern or UI Pattern: Activity Stream for more details about that. in the “activity stream” model, the thing you are consuming is the Activity itself, directly, and not specifically a Note or Article or whatever.

if you are only interested in what someone has Created or Announced, then you might query your inbox or their outbox for items which have a type of Create or Announce. this query doesn’t need to be done within the client; it can be done through a service endpoint that allows querying or serves a pre-filtered result set or cache. Imagine the equivalent of a SPARQL endpoint that let you DESCRIBE ?activity WHERE {{?activity rdf:type ?t} FILTER(?t IN (as:Create, as:Announce))} and serialized this in JSON-LD and wrapped it in an as:OrderedCollection or whatever. it’s just that whatever the server doesn’t do for you, the client will have to do instead.

this is mostly a distribution and caching issue, and also a bit of an eager-loading/preloading issue. when browsing a web of resources, you don’t generally optimistically prefetch resources unless you have a reason to be fetching them – in HTML, this would be your links of rel=stylesheet or rel=preload. there isn’t really a reason to go and fetch the entire chain of as:inReplyTo all the way up until you hit a dead end. you can selectively navigate upward as little or as much as you want. in reddit, a typical default parameter for this is to fetch the last 3 parents. it’s perfectly fine to only load these links on-demand as the user browses to them. the user-agent’s role is to make requests on behalf of the user, to provide them with the information they want to see. me navigating to a single web page does not mean i want to spider half the entire web. there’s nothing inherently special about a reply tree that warrants crawling/spidering the entire thing in all cases ahead of time.

you’re limited in how efficient you can make this because the whole thing is inherently inefficient. again, an indexing or discovery service or aggregator or crawler can go fetch you an arbitrary web of resources and try to stitch them together by their as:inReplyTo relations, but there are probably better things you can be doing instead.