diff --git a/README.md b/README.md index 7184d2bc..2a0cf1a7 100644 --- a/README.md +++ b/README.md @@ -278,10 +278,12 @@ you may receive a old message in real time - but for old messages, it makes sens all standard options are supported. -### db.createUserStream ({id: feed_id, lt,lte,gt,gte: sequence, reverse,old,live,raw: boolean, limit: number}) +### db.createUserStream ({id: feed_id, lt,lte,gt,gte: sequence, reverse,old,live,raw: boolean, limit: number, private: boolean}) -`createUserStream` is like `createHistoryStream`, except all options are supported. Local access is allowed, but not -remote anonymous access. `createUserStream` does decrypt private messages. +`createUserStream` is like `createHistoryStream`, except all options are +supported. Local access is allowed, but not remote anonymous access. +`createUserStream` can decrypt private messages if you pass the option +`{ private: true }`. ### db.links({source: feedId?, dest: feedId|msgId|blobId?, rel: string?, meta: true?, keys: true?, values: false?, live:false?, reverse: false?}) -> PullSource diff --git a/create.js b/create.js index e223f6eb..13724d53 100644 --- a/create.js +++ b/create.js @@ -84,8 +84,8 @@ module.exports = function create (path, opts, keys) { if (err) return cb(err) if (!isPrivate) { - if (meta) cb(null, { key, value: data.value, timestamp: data.timestamp }) - else cb(null, data.value) + if (meta) cb(null, { key, value: u.originalValue(data.value), timestamp: data.timestamp }) + else cb(null, u.originalValue(data.value)) } else { const result = db._unbox(data, unbox) @@ -133,12 +133,25 @@ module.exports = function create (path, opts, keys) { } db.createRawLogStream = function (opts) { - return db.stream(opts) + return pull( + db.stream(opts), + pull.map(({ seq, value }) => { + return { seq, value: u.originalData(value) } + }) + ) } // pull in the features that are needed to pass the tests // and that sbot, etc uses but are slow. extras(db, opts, keys) + // - adds indexes: links, feed, time + // - adds methods: + // - db.createLogStream + // - db.createFeedStream + // - db.creareUserStream + // - db.latest + // - db.latestSequence + // - db.getLatest // writeStream - used in (legacy) replication. db.createWriteStream = function (cb) { diff --git a/index.js b/index.js index 4a77bf39..5d855242 100644 --- a/index.js +++ b/index.js @@ -5,7 +5,9 @@ var osenv = require('osenv') var mkdirp = require('mkdirp') var rimraf = require('rimraf') var valid = require('./lib/validators') -var pkg = require('./package.json') +var version = require('./package.json').version +var help = require('./help') + const pull = require('pull-stream') const pullNotify = require('pull-notify') const pullCat = require('pull-cat') @@ -35,7 +37,7 @@ var manifest = { status: 'sync', getVectorClock: 'async', version: 'sync', - help: 'sync', + help: 'sync' } module.exports = { @@ -106,42 +108,23 @@ module.exports = { ssb.since(sequenceNotifier) return self = { - id : feed.id, keys : opts.keys, + id : feed.id, - ready : function () { - return ssb.ready.value - }, - - progress : function () { - return ssb.progress + whoami : () => { + return { id: feed.id } }, - - status : function () { + version : () => version, + ready : () => ssb.ready.value, + progress : () => ssb.progress, + status : () => { return { - progress: self.progress(), + progress: ssb.progress, db: ssb.status, sync: since() } }, - version : function () { - return pkg.version - }, - - createSequenceStream: () => { - // If the initial value is `undefined` we want it to be `-1`. - // This is because `-1` is a magic sequence number for an empty log. - const initialValue = ssb.since.value !== undefined - ? ssb.since.value - : -1 - - return pullCat([ - pull.values([initialValue]), - sequenceNotifier.listen() - ]) - }, - //temporary! _flumeUse : function (name, flumeview) { ssb.use(name, flumeview) @@ -164,20 +147,31 @@ module.exports = { getLatest : valid.async(ssb.getLatest, 'feedId'), latestSequence : valid.async(ssb.latestSequence, 'feedId'), createFeed : ssb.createFeed, - whoami : function () { return { id: feed.id } }, createFeedStream : valid.source(ssb.createFeedStream, 'readStreamOpts?'), createHistoryStream : valid.source(ssb.createHistoryStream, ['createHistoryStreamOpts'], ['feedId', 'number?', 'boolean?']), createLogStream : valid.source(ssb.createLogStream, 'readStreamOpts?'), createUserStream : valid.source(ssb.createUserStream, 'createUserStreamOpts'), + createSequenceStream : () => { + // If the initial value is `undefined` we want it to be `-1`. + // This is because `-1` is a magic sequence number for an empty log. + const initialValue = ssb.since.value !== undefined + ? ssb.since.value + : -1 + + return pullCat([ + pull.values([initialValue]), + sequenceNotifier.listen() + ]) + }, links : valid.source(ssb.links, 'linksOpts'), - sublevel : ssb.sublevel, + // sublevel : ssb.sublevel, // Disabled as does not appear to be used messagesByType : valid.source(ssb.messagesByType, 'string|messagesByTypeOpts'), createWriteStream : ssb.createWriteStream, getVectorClock : ssb.getVectorClock, getAtSequence : ssb.getAtSequence, addBoxer : ssb.addBoxer, addUnboxer : ssb.addUnboxer, - help : function () { return require('./help') } + help : () => help } } } diff --git a/test/box-unbox.js b/test/box-unbox.js index 2459bff7..08602a95 100644 --- a/test/box-unbox.js +++ b/test/box-unbox.js @@ -3,11 +3,12 @@ var tape = require('tape') var pull = require('pull-stream') var ssbKeys = require('ssb-keys') var box1 = require('ssb-private1/box1') +const { promisify } = require('util') var createSSB = require('./create-ssb') var { originalValue } = require('../util') -module.exports = function (opts) { +module.exports = function () { var alice = ssbKeys.generate() var bob = ssbKeys.generate() var charles = ssbKeys.generate() @@ -34,13 +35,12 @@ module.exports = function (opts) { tape('error when trying to encrypt without boxer', (t) => { t.plan(2); const darlene = ssbKeys.generate() - const darleneSSB = createSSB('test-ssb-darlene', { keys: darlene }) const darleneFeed = ssb.createFeed(darlene) darleneFeed.add( { type: "error", recps: [alice, darlene] }, (err, msg) => { - t.ok(err); - t.notOk(msg); + t.ok(err); + t.notOk(msg); t.end() }) }) @@ -52,7 +52,7 @@ module.exports = function (opts) { var postObserved var listener = ssb.post(msg => { postObserved = msg }) - feed.add(boxed, function (err, msg) { + feed.add(boxed, function (err) { if (err) throw err t.notOk(err) @@ -145,7 +145,7 @@ module.exports = function (opts) { var listener = ssb.post(msg => { postObserved = msg }) // secret message sent to self - feed.add({ type: 'secret2', secret: "it's a secret!", recps: feed.id }, function (err, msg) { + feed.add({ type: 'secret2', secret: "it's a secret!", recps: feed.id }, function (err) { if (err) throw err t.notOk(err) @@ -165,7 +165,7 @@ module.exports = function (opts) { ) listener() - t.true(typeof postObserved.value.content === 'string', 'post obs messages should not be decrypted') + t.true(typeof postObserved.value.content === 'string', 'db.post obs messages should not be decrypted') t.end() }) @@ -264,7 +264,7 @@ module.exports = function (opts) { }) tape('addUnboxer (simple)', function (t) { - const unboxer = function (ciphertext, value) { + const unboxer = function (ciphertext) { if (!ciphertext.endsWith('.box.hah')) return const base64 = ciphertext.replace('.box.hah', '') @@ -301,12 +301,12 @@ module.exports = function (opts) { done() }, 500) }, - key: function (ciphertext, value) { + key: function (ciphertext) { if (!ciphertext.endsWith('.box.hah')) return return '"the msgKey"' }, - value: function (ciphertext, msgKey) { + value: function (ciphertext) { const base64 = ciphertext.replace('.box.hah', '') return JSON.parse( Buffer.from(base64, 'base64').toString('utf8') @@ -319,19 +319,87 @@ module.exports = function (opts) { const content = { type: 'poke', reason: 'why not', - recps: [ '!test' ] + recps: [ '!test' ], + myFriend: alice.id// Necessary to test links() } const ciphertext = Buffer.from(JSON.stringify(content)).toString('base64') + '.box.hah' feed.publish(ciphertext, (_, msg) => { t.true(initDone, 'unboxer completed initialisation before publish') - ssb.get({ id: msg.key, private: true, meta: true }, (err, msg) => { - if (err) throw err + ssb.get({ id: msg.key, private: true, meta: true }, async (err, msg) => { + t.error(err) t.true(initDone, 'unboxer completed initialisation before get') t.deepEqual(msg.value.content, content, 'auto unboxing works') + + const assertBoxed = (methodName, message) => { + if (typeof message.key === 'string') { + t.equal(message.key, msg.key, `${methodName}() returned correct message`) + t.equal(typeof message.value.content, 'string', `${methodName}() does not unbox by default`) + } else { + t.equal(typeof message.content, 'string', `${methodName}() does not unbox by default`) + } + } + + const assertBoxedAsync = async (methodName, options) => { + assertBoxed(methodName, await promisify(ssb[methodName])(options)) + if (typeof options === 'object' && Array.isArray(options) === false) { + assertBoxed(methodName, await promisify(ssb[methodName])({ ...options, private: false } )) + } + } + + // This tests the default behavior of `ssb.get()`, which should never + // decrypt messages by default. This is **very important**. + await assertBoxedAsync('get', msg.key) + await assertBoxedAsync('get', { id: msg.key }) + await assertBoxedAsync('get', { id: msg.key, meta: true }) + await assertBoxedAsync('getAtSequence', [msg.value.author, msg.value.sequence]) + await assertBoxedAsync('getLatest', msg.value.author) + + const assertBoxedSourceOnce = (methodName, options) => new Promise((resolve) => { + pull( + ssb[methodName](options), + pull.collect((err, val) => { + t.error(err, `${methodName}() does not error`) + switch (methodName) { + case 'createRawLogStream': + assertBoxed(methodName, val[0].value) + break; + case 'createFeedStream': + case 'createUserStream': + case 'messagesByType': + // Apparently some methods take `{ private: false }` to mean + // "don't return any private messages". :/ + if (options.private === undefined) { + assertBoxed(methodName, val[0].value) + } + break + default: + assertBoxed(methodName, val[0]) + } + resolve() + }) + ) + }) + + // Test the default **and** `{ private: false }`. + const assertBoxedSource = async (methodName, options) => { + await assertBoxedSourceOnce(methodName, options) + await assertBoxedSourceOnce(methodName, { ...options, private: false }) + } + + await assertBoxedSource('createLogStream', { limit: 1, reverse: true }) + await assertBoxedSource('createHistoryStream', { id: msg.value.author, seq: msg.value.sequence, reverse: true}) + await assertBoxedSource('messagesByType', { type: 'poke', limit: 1, reverse: true }) + await assertBoxedSource('createFeedStream', { id: msg.value.author, seq: msg.value.sequence, reverse: true}) + await assertBoxedSource('createUserStream', { id: msg.value.author, seq: msg.value.sequence, reverse: true}) + await assertBoxedSource('links', { source: msg.value.author, limit: 1, values: true}) + await assertBoxedSource('createRawLogStream', { source: msg.value.author, limit: 1, reverse: true, values: true}) + // createRawLogStream currently not exported as a method + t.end() + }) }) }) diff --git a/test/manifest.js b/test/manifest.js new file mode 100644 index 00000000..32ed4a11 --- /dev/null +++ b/test/manifest.js @@ -0,0 +1,23 @@ +'use strict' +var tape = require('tape') +var { manifest, init } = require('../') + +module.exports = function () { + tape('manifest', t => { + const _api = {} + const opts = { + path: `/tmp/ssb-manifest-test-${Date.now()}-${Math.random()}` + } + const api = init(_api, opts) + + Object.keys(api).forEach(m => console.log(m)) + + Object.keys(manifest).forEach(method => { + t.equal(typeof api[method], 'function', `api.${method}`) + }) + + t.end() + }) +} + +if (!module.parent) { module.exports({}) }