A public board
This recipe builds a public board in the spirit of a Usenet newsgroup: one peer hosts it, posts are readable by anyone, and readers who are online hear about new posts at once. Ricochet offers two building blocks for it, and this page shows both.
| Public mailbox | Collaborative feed | |
|---|---|---|
| Who may post | The owner, and peers granted write access | Anyone |
| Who may read | Anyone | Anyone (feeds are public by default) |
| Push to readers | Yes, on GossipSub | No; readers poll |
| Reader position | Kept by the server per reader | Kept by the reader (sequence number) |
| Lifetime of a post | Message expiry and mailbox retention | Until the feed is deleted, up to the per-feed entry cap |
| Moderation | Grant and revoke posters | None; filter on CreatedBy when reading |
Design A: a public mailbox
Section titled “Design A: a public mailbox”The owner creates a public mailbox and grants posting rights:
// Sketch: the board owner's setup.err := owner.CreateMailbox(ctx, "boards/gardening", wire.MailboxPublic, client.WithMailboxMaxMessages(1000), client.WithRetentionDays(30), client.WithRetentionCount(900), // keep the newest 900 when the cap is reached)err = owner.GrantAccess(ctx, "boards/gardening", posterID, wire.AccessWriteOnly)WithRetentionCount below the cap makes the board a rolling window: when it fills, the server prunes the
oldest posts instead of refusing new ones.
A poster sends a message to the owner’s folder. Posts are public, so they are not encrypted; make them persistent, and give them an expiry as long as the board’s retention, because the default expiry is seven days:
// Sketch: posting.post, _ := json.Marshal(map[string]string{"subject": "Tomatoes", "body": "Started seeds today."})res, err := poster.SendMessage(ctx, ownerID, post, client.WithFolderPath("boards/gardening"), client.WithPersistent(true), client.WithExpiry(30*24*time.Hour),)if err == nil { err = res.Err() // client.ErrForbidden without a grant}A reader subscribes to the board’s GossipSub topic for push, and retrieves. The server keeps a cursor per
reader on a public mailbox, so each retrieve without WithFromSequence returns only posts that reader has
not retrieved yet:
// Sketch: a reader. h is the reader's host, connected to the board's server.ps, err := pubsub.NewGossipSub(ctx, h)if err != nil { return err}topic, err := ps.Join("/ricochet/mailbox/" + ownerID.String() + "/boards/gardening")if err != nil { return err}sub, err := topic.Subscribe()if err != nil { return err}
fetch := func() { page, err := reader.RetrievePage(ctx, client.WithTargetPeer(ownerID), client.WithRetrieveFolderPath("boards/gardening")) for err == nil { show(page.Messages) // your function if !page.HasMore { return } page, err = reader.RetrievePage(ctx, client.WithTargetPeer(ownerID), client.WithRetrieveFolderPath("boards/gardening")) }}
fetch() // catch up on startfor { if _, err := sub.Next(ctx); err != nil { return err } fetch()}To show the whole board again, for instance on a new device, retrieve with WithFromSequence(1). Readers
cannot acknowledge or delete posts; the owner can, with DeleteMessages, which is how moderation removes a
post. Poll on a timer as well, since GossipSub notifications are best effort.
Design B: a collaborative feed
Section titled “Design B: a collaborative feed”The owner creates a collaborative feed, and anyone appends to it:
// Sketch: a feed-based board.err := owner.CreateFeed(ctx, "boards/gardening", "Gardening", "Seeds, soil and weather", client.WithCollaborative())
seq, err := poster.AppendToFeed(ctx, ownerID, "boards/gardening", post, "post")Readers track the last sequence number themselves and poll:
entries, hasMore, err := reader.GetFeedEntries(ctx, ownerID, "boards/gardening", client.WithFeedEntryFrom(lastSeq+1), client.WithFeedEntryLimit(200),)for _, e := range entries { show(e.CreatedBy, e.Content) // CreatedBy is the poster's peer ID, set by the server lastSeq = e.Seq}A reader following many boards fetches them all in one request with GetMultiFeedEntries, one
client.BatchFeedQuery per board with its own FromSequence. Use the entry type to separate posts from
replies, and WithFeedEntryType to read only one kind.
A feed has no push. If readers need it, combine the two designs: the owner runs a small bot that follows the feed and sends a short persistent message to a public mailbox for each new entry, and readers subscribe to that mailbox’s topic.
Finding boards
Section titled “Finding boards”Publish the board’s address (owner peer ID, folder or feed path, and the server’s address) wherever your
users look. One option is the owner’s directory listing: put the board paths in Extras when calling
JoinDirectory, and readers find them with BrowseDirectory. See Directory & presence.