Persistent Threads

Support persistent message threads in your app

Persistent threads are threads where messages are stored in and made available by a persistent OrbitDB feed store shared between one or many users. Messages persist unless they are explicitly removed by the author or a moderator.

Get Started

For full messaging control, add persistent threads to your project using the steps below.

  1. Add posts

Want to quickly get started with persistent threads? Add the 3Box Comments UI plugin to easily add an open commenting section to your project.

1. Configure your threads

Before getting started, you should consider a few configuration options for your persistent threads, which will make later steps more simple.

You only need to perform this step if you are creating a new persistent thread. If you want to join an existing thread, then proceed to Step 3 (join an existing thread).

First, decide on a persistent thread type.

The first step in configuring a persistent thread is making sure you actually need a Persistent Thread, and not a Ghost Thread.

After you're sure that persistent threads are best suited to your use case, you need to decide which type of persistent thread is most appropriate based on your write and read access control requirements.

Which thread should you use?

Read: Public

Read: Restricted (multi-user)

Read: Restricted (single user)

Write: Public

Open

-

-

Write: Restricted (multi-user)

Members Open

Members Confidential

-

Write: Restricted (single user)

Personal Open

-

Personal Confidential

Then, choose a name for your space.

Persistent threads are stored in OrbitDB feed stores, and references to these threads are stored publicly in your application's space. These spaces are created and exist inside the 3Box of each user that joins the thread, not in one single global space. Before proceeding you should choose a name for the space that you will use to store your app's thread references.

In most cases, the name of the space should be the name of your application.

We recommend simply using the name of your application for this threads space, unless you want your application's threads kept in a different space for some reason. This space is an OrbitDB key-value store, so you can also keep other information and data related to your application inside it.

For example, if your application is called CheeseWizards, we would recommend your space be named cheeseWizards.

Finally, choose a naming convention for your individual threads.

For thread name, we recommend creating some standard convention that allows your application to easily create and manage many threads. While you can decide what this convention is, you may consider using some URL, a topic, or something else unique.

2. Create a new thread

Now that you have decided on the configuration options for your persistent threads in Step 1, the next step in actually adding them to your application is allowing users to create new threads. There are slightly different ways to create threads depending on its type.

To perform interactive operations on threads, such as creating a thread, joining a thread, posting messages, removing messages, adding moderators, and adding members, you must first authenticate the space wherein the thread is stored.

2a. Create an Open Thread

To create an open thread, you can simply join the thread using the joinThread method. This will implicitly use the default moderation options where the current user is the firstModerator and members is false. The space object and thread names should reflect your space name and thread name conventions defined in Step 1.

const thread = await space.joinThread('myThread')

Alternatively, you can create an open thread using more explicit custom configurations, which allows you to set another user as the firstModerator.

const thread = await space.joinThread('myThread', {
firstModerator: <ethereum-address or 3id>,
members: false
})

2b. Create a Members Open Thread

To create a members open thread, you can simply join the thread by using the joinThread method and passing the following configuration options: firstModerator, which requires a 3ID of the first moderator, and a members boolean set to true. The space object and thread names should reflect your space name and thread name conventions defined in Step 1.

const thread = await space.joinThread('myThread', {
firstModerator: <ethereum-address or 3id>,
members: true
})

If you are creating a members open thread, you will likely want to add other members around the time of creation so they can add messages to the thread. Use the addMember method to allow moderators to add members to this thread. Note this is performed on the thread object.

await thread.addMember(<ethereum-address or 3id>)

2c. Create a Personal Open Thread

To create a personal open thread, you can simply join the thread by using the joinThread method and passing a members true boolean. After creating the thread, the user should not invite any other members so they can remain the only user with write permissions. The space object and thread names should reflect your space name and thread name conventions defined in Step 1.

const thread = await space.joinThread('myThread', {
members: true
})

2d. Create a Members Confidential or Personal Confidential Thread

To create a members confidential or personal confidential thread, use the createConfidentialThread method instead of the joinThread method. The space object and thread names should reflect the space name and thread naming convention chosen in Step 1.

const thread = await space.createConfidentialThread('myConfThread')

For a members confidential thread, you will likely want to add other members around the time of creation so they can read from and write messages to the thread. Use the addMember method to allow moderators to add members to this thread. Note this is performed on the thread object.

await thread.addMember(<ethereum-address or 3id>)

For a personal confidential thread, you will not add additional members to the thread.

3. Join an existing thread

Persistent threads are identified within the 3Box network in two ways: by a unique thread address, or by a unique set of configuration options. While either can be used to join an existing thread, we recommend joining by the thread address. This is especially true for members confidential and personal confidential threads since we have abstracted away much of the config complexity.

To join an existing thread, the easiest way is by its address using the joinThreadByAddress() method.

const thread = await space.joinThreadByAddress('/orbitdb/zdpuAp5QpBKR4BBVTvqe3KXVcNgo4z8Rkp9C5eK38iuEZj3jq/3box.thread.testSpace.testThread')

For open threads, join the thread to read messages and post new messages.

For members open and personal open threads, join the thread to read messages but you will only be able to post new messages if you have first been added by a moderator.

For members confidential and personal confidential threads, joining the thread will not allow you to read messages or post new messages unless you have first been added by a moderator.

Option 2: Join Thread by Configs

Alternative to joining by thread address, you can also join open, members open, and personal open threads by their configs using the joinThread() method.

const publicThread = await space.joinThread('myThread', { firstModerator: 'some3ID', members: true })

Discover Thread Address

Users can discover the thread address in many ways depending on the app's implementation of thread discovery, however users within the thread can use the code below to get the thread address and send it to new users in whatever way they choose.

An address of a thread can be found as follows once joined.

const threadAddress = thread.address

4. View posts

There are three options to getting the messages in a thread. Each is appropriate in certain contexts and circumstances for your application and use case.

Option 1: Auth, then thread.getPosts()

This option for getting messages in a thread is appropriate when:

  • you may not know the address or configurations of the thread

  • you have already authenticated a user using the openBox and openSpace methods

  • the user has already joined the thread

  • you want to dynamically display updates to the thread

To get all posts in a thread, use thread.getPosts()

const posts = await thread.getPosts()
console.log(posts)
// you can also specify a number of posts you want
const posts = await thread.getPosts(20)
console.log(posts)

Listen for new updates

After you have gotten posts in a thread, you will likely want to listen for updates to the thread so you can update your application's UI. To listen for updates to a thread, such as new posts, use thread.onUpdate(). This will allow you to immediately display new messages posted to the thread in your application.

thread.onUpdate(myCallbackFunction)

Option 2: No auth + box.openThread() + thread.getPosts()

This option for getting messages in a thread is appropriate when:

  • you know the address or configurations of the thread

  • your user has not yet authenticated to their 3Box and space

  • you want to dynamically display updates to the thread

  • you're getting open, members open, or personal open threads

Create a 3Box instance by calling Box.create(). To get all posts in a thread, use box.openThread() to instantiate the thread object, then you can call the thread.getPosts() methods from above.

When using the box.openThread() method, you should pass in the space name, thread name, firstModerator, and members configs to ensure you're opening the correct thread.

const thread = await box.openThread('myDapp', 'myThread', { firstModerator: 'did:3:bafy...', members: true })
const posts = await thread.getPosts()
console.log(posts)

After you have called openThread and getPosts methods, you can use the thread.onUpdate() method above to listen for updates to the thread and dynamically sync new messages to your application's UI.

Option 3: No auth + Box.getThread()

This option for getting messages in a thread is appropriate when:

  • you know the address or configurations of the thread

  • your user has not yet authenticated to their 3Box and space

  • you do not need to sync updates to the thread in realtime to your application

  • you're getting open, members open, or personal open threads

To statically get all posts in a thread, use either the Box.getThreadByAddress() or the Box.getThread() method.

const thread = await Box.getThreadByAddress('/orbitdb/zdpuAp5QpBKR4BBVTvqe3KXVcNgo4z8Rkp9C5eK38iuEZj3jq/3box.thread.testSpace.testThread')
const thread = await Box.getThread('myDapp', 'myThread', { firstModerator: 'did:3:bafy...', members: true })

With this method, you cannot use the thread.onUpdate method to listen for updates. Your user will need to refresh the page to receive new messages in the thread.

5. Add posts

To post in a thread, a user must first either create or join the thread using the steps above. After they have joined a thread, they can add a message to the thread using the thread.post() method. The author of the message will be the user's 3ID (3Box DID). Everyone who has permission to read the thread will have access to the message.

await thread.post('hello world')

6. Moderation Options and Configurations

For more information on adding moderators, adding members, removing posts, and more, read the full Messaging API documentation.