ssb-ngi-pointer
to demonstrate the outcome of the work done by the SSB NGI Pointer team during 2020 and 2021. The SSB NGI Pointer team is no longer active because we completed our grant project.
An implementation of the ssb meta feed spec in JS as a secret stack plugin.
Prerequisites:
- Requires Node.js 10 or higher
- Requires
ssb-db2
npm install --save ssb-meta-feeds
Add this plugin like this:
const sbot = SecretStack({ appKey: caps.shs })
.use(require('ssb-db2'))
+ .use(require('ssb-meta-feeds'))
// ...
Let's start by creating a root meta feed, necessary for using this module. It lives alongside your existing "classical" feed, which we'll refer to as main feed.
sbot.metafeeds.findOrCreate((err, metafeed) => {
console.log(metafeed) // { seed, keys }
})
Now this has created the seed
, which in turn is used to generate an ssb-keys
keys
object. The seed
is actually also published on the main feed as a
private message to yourself, to allow recovering it in the future.
There can only be one root meta feed, so even if you call findOrCreate
many
times, it will not create duplicates, it will just load the meta feed
{ seed, keys }
.
Now you can create subfeeds under that root meta feed by passing three
arguments to findOrCreate
, before the callback, like this:
const details = {
feedpurpose: 'mygame',
feedformat: 'classic',
metadata: {
score: 0,
whateverElse: true,
},
}
const visit = f => f.feedpurpose === 'mygame' && f.feedformat === 'classic'
sbot.metafeeds.findOrCreate(metafeed, visit, details, (err, subfeed) => {
console.log(subfeed)
// {
// feedpurpose: 'mygame',
// subfeed: '@___________________________________________=.ed25519',
// keys: {
// curve,
// public,
// private,
// id
// }
// }
})
This has created a keys
object for a new subfeed, which you can use to publish
application-specific messages (such as for a game). The details
argument
always needs feedpurpose
and feedformat
(supports classic
for ed25519
normal SSB feeds, and bendybutt-v1
).
The subfeed is created only if it has not been found on your (given) metafeed
.
Notice that to find an existing matching subfeed, we pass the visit
function
on the 2nd argument. The f
there is an object that describes the subfeed, with
the fields:
feedformat
: the string'classic'
or the string'bendybutt-v1'
feedpurpose
: same string as used to create the subfeedsubfeed
: the SSB identifier for this subfeedkeys
: the ssb-keys compatible{ curve, public, private, id }
objectmetadata
: the same object used when creating the subfeed
Given a feedId
that is presumed to be a subfeed of some meta feed, this API
fetches the metadata associated with the creation of this subfeed on its parent
meta feed, and returns (via the callback cb
on the second argument) an object
with the following shape:
{ feedpurpose, feedformat, metafeed, metadata }
Similar to findById
, but returns synchronously. sbot.metafeeds.loadState(cb)
first, and wait
for cb
to be called.
You can also call sbot.metafeeds.ensureLoaded(feedId, cb)
on an individual
basis to make sure that findByIdSync
will operate at the correct time when the
feedId
's metadata has been processed in the local database.
Returns a pull-stream source of all "branches" in the meta feed trees.
A "branch" is an array where the first item is the root meta feed and the subsequent items are the children and grandchildren (and etc) of the root. A branch looks like this:
[
[rootMetafeedId, null],
[childMetafeedId, childDetails],
[grandchildMetafeedId, grandchildDetails],
]
Or in general, an Array<[FeedId, Details | null]>
. The details object has
the shape { feedpurpose, feedformat, metafeed, metadata }
like in findById
.
branchStream
will emit all possible branches, which means sub-branches are
included. For instance, in the example above, branchStream
would emit:
[
[rootMetafeedId, null]
]
and
[
[rootMetafeedId, null],
[childMetafeedId, childDetails],
]
and
[
[rootMetafeedId, null],
[childMetafeedId, childDetails],
[grandchildMetafeedId, grandchildDetails],
]
The opts
argument can have the following properties:
opts.root
String - a feed ID for a meta feed, only branches that are descendants of this feed ID would appear in the pull-stream source, otherwise all branches from all possible root meta feeds will be included. (Default:null
)opts.old
Boolean - whether or not to include currently loaded (byloadState
) trees. (Default:false
)opts.live
Boolean - whether or not to include subsequent meta feed trees during the execution of your program. (Default:true
)opts.tombstoned
Boolean - iffalse
, no tombstoned branches are included in the results; iftrue
, only tombstoned branches are included; ifnull
, all branches are included regardless of tombstoning. (Default:null
)
Looks for the first subfeed of metafeed
that satisfies the condition in
visit
, or creates it matching the properties in details
.
This is strictly concerned with meta feeds and sub feeds that you own, not with those that belong to other peers.
metafeed
can be either null
or a meta feed object { seed, keys }
(as
returned by getRoot()
). If it's null, then the root meta feed will be created,
if and only if it does not already exist. If it's null and the root meta feed
exists, the root meta feed will be returned via the cb
. Alternatively, you can
call this API with just the callback: sbot.metafeeds.findOrCreate(cb)
.
visit
can be either null
or a function of the shape
({feedpurpose,subfeed,metadata,keys}) => boolean
. If it's null, then one
arbitrary subfeed under metafeed
is returned.
details
can be null
only if if metafeed
is null, but usually it's an
object with the properties:
feedpurpose
: any string to characterize the purpose of this new subfeedfeedformat
: the string'classic'
or the string'bendybutt-v1'
metadata
: an optional object containing other fields. One can includerecps
here to encrypt the message
The response is delivered to the callback cb
, where the 1st argument is the
possible error, and the 2nd argument is the created feed (which can be either
the root meta feed { seed, keys }
or a sub feed
{ feedpurpose, subfeed, keys, metadata }
).
_Looks for the first subfeed of metafeed
that satisfies the condition in
visit
and, if found, tombstones it with the string reason
.
This is strictly concerned with meta feeds and sub feeds that you own, not with those that belong to other peers.
metafeed
must be a meta feed object { seed, keys }
(as returned by
getRoot()
).
visit
must be a function of the shape ({feedpurpose, subfeed, metadata, keys}) => boolean
.
reason
must be a string to describe why the found feed is being tombstoned.
The callback is called with true
on the 2nd argument if tombstoning suceeded,
or called with an error object on the 1st argument if it failed.
Looks for the root meta feed declared by your main feed, and returns it (as
{ seed, keys}
) via the callback cb
if it exists.
If it does not exist, this API will not create the root meta feed.
Exposed via the internal API.
Validate a single meta feed message.
Extracts the contentSection
from the given msg
object and calls
validateSingle()
to perform validation checks.
If provided, the hmacKey
is also given as input to the validateSingle()
function call. hmacKey
may be null
or a valid HMAC key supplied as a
Buffer
or string
.
The response is a boolean: true
if validation is successful, false
if
validation fails in any way. Note that this function does not return the
underlying cause of the validation failure.
Validate a single meta feed message contentSection
according to the criteria
defined in the specification.
contentSection
must be an array of content
and contentSignature
. If a
string
is provided (representing an encrypted message, for instance) an error
will be returned; an encrypted contentSection
cannot be validated.
hmacKey
may be null
or a valid HMAC key supplied as a Buffer
or string
.
The response will be undefined
(for successful validation) or an Error
object with a message
describing the error.
Validates a metafeed/announce
message expected to be published on "main"
feeds which are in the classic format, but are signed by a meta feed according
to the ssb meta feed spec.
The response will be undefined
(for successful validation) or an Error
object with a message
describing the error.
LGPL-3.0