mirror of https://codeberg.org/pzp/pzp-db.git
all tests pass
This commit is contained in:
parent
ddbb3f367e
commit
83d941e2dc
|
@ -1,39 +1,44 @@
|
||||||
|
const FeedV1 = require('./feed-v1')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('./plugin').Rec} Rec
|
||||||
|
*/
|
||||||
|
|
||||||
function ciphertextStrToBuffer(str) {
|
function ciphertextStrToBuffer(str) {
|
||||||
const dot = str.indexOf('.')
|
const dot = str.indexOf('.')
|
||||||
return Buffer.from(str.slice(0, dot), 'base64')
|
return Buffer.from(str.slice(0, dot), 'base64')
|
||||||
}
|
}
|
||||||
|
|
||||||
function decrypt(msg, ssb, config) {
|
/**
|
||||||
const { author, previous, content } = msg.value
|
* @param {Rec} rec
|
||||||
if (typeof content !== 'string') return msg
|
* @param {any} peer
|
||||||
|
* @param {any} config
|
||||||
|
* @returns {Rec}
|
||||||
|
*/
|
||||||
|
function decrypt(rec, peer, config) {
|
||||||
|
const msgEncrypted = rec.msg
|
||||||
|
const { metadata, content } = msgEncrypted
|
||||||
|
const { who, prev } = metadata
|
||||||
|
if (typeof content !== 'string') return rec
|
||||||
|
|
||||||
const encryptionFormat = ssb.db.findEncryptionFormatFor(content)
|
const encryptionFormat = peer.db.findEncryptionFormatFor(content)
|
||||||
if (!encryptionFormat) return msg
|
if (!encryptionFormat) return rec
|
||||||
|
|
||||||
const feedFormat = ssb.db.findFeedFormatForAuthor(author)
|
|
||||||
if (!feedFormat) return msg
|
|
||||||
|
|
||||||
// Decrypt
|
// Decrypt
|
||||||
const ciphertextBuf = ciphertextStrToBuffer(content)
|
const ciphertextBuf = ciphertextStrToBuffer(content)
|
||||||
const opts = { keys: config.keys, author, previous }
|
const opts = { keys: config.keys, author: who, previous: prev }
|
||||||
const plaintextBuf = encryptionFormat.decrypt(ciphertextBuf, opts)
|
const plaintextBuf = encryptionFormat.decrypt(ciphertextBuf, opts)
|
||||||
if (!plaintextBuf) return msg
|
if (!plaintextBuf) return rec
|
||||||
|
|
||||||
// Reconstruct KVT in JS encoding
|
// Reconstruct KVT in JS encoding
|
||||||
const nativeMsg = feedFormat.toNativeMsg(msg.value, 'js')
|
const msgDecrypted = FeedV1.fromPlaintextBuffer(plaintextBuf, msgEncrypted)
|
||||||
// TODO: feedFormat.fromDecryptedNativeMsg() should NOT mutate nativeMsg
|
|
||||||
// but in the case of ssb-classic, it is
|
|
||||||
const msgVal = feedFormat.fromDecryptedNativeMsg(
|
|
||||||
plaintextBuf,
|
|
||||||
{ ...nativeMsg, value: { ...nativeMsg.value } }, // TODO revert this
|
|
||||||
'js'
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: msg.key,
|
id: rec.id,
|
||||||
value: msgVal,
|
msg: msgDecrypted,
|
||||||
timestamp: msg.timestamp,
|
received: rec.received,
|
||||||
meta: {
|
misc: {
|
||||||
|
...rec.misc,
|
||||||
private: true,
|
private: true,
|
||||||
originalContent: content,
|
originalContent: content,
|
||||||
encryptionFormat: encryptionFormat.name,
|
encryptionFormat: encryptionFormat.name,
|
||||||
|
@ -41,16 +46,16 @@ function decrypt(msg, ssb, config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function reEncrypt(msg) {
|
function reEncrypt(rec) {
|
||||||
return {
|
return {
|
||||||
key: msg.key,
|
id: rec.id,
|
||||||
value: { ...msg.value, content: msg.meta.originalContent },
|
msg: { ...rec.msg, content: rec.misc.originalContent },
|
||||||
timestamp: msg.timestamp,
|
received: rec.received,
|
||||||
...(msg.meta.size
|
...(rec.misc.size
|
||||||
? {
|
? {
|
||||||
meta: {
|
misc: {
|
||||||
offset: msg.meta.offset,
|
offset: rec.misc.offset,
|
||||||
size: msg.meta.size,
|
size: rec.misc.size,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: null),
|
: null),
|
||||||
|
|
|
@ -2,25 +2,54 @@ const blake3 = require('blake3')
|
||||||
const base58 = require('bs58')
|
const base58 = require('bs58')
|
||||||
const stringify = require('fast-json-stable-stringify')
|
const stringify = require('fast-json-stable-stringify')
|
||||||
|
|
||||||
function getMsgHashBuf(nativeMsg) {
|
/**
|
||||||
const { metadata, signature } = nativeMsg
|
* @typedef {import('./index').Msg} Msg
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Msg} msg
|
||||||
|
* @returns {Buffer}
|
||||||
|
*/
|
||||||
|
function getMsgHashBuf(msg) {
|
||||||
|
const { metadata, sig } = msg
|
||||||
const metadataBuf = Buffer.from(stringify(metadata), 'utf8')
|
const metadataBuf = Buffer.from(stringify(metadata), 'utf8')
|
||||||
const sigBuf = base58.decode(signature)
|
const sigBuf = base58.decode(sig)
|
||||||
return blake3
|
return blake3.hash(Buffer.concat([metadataBuf, sigBuf])).subarray(0, 16)
|
||||||
.hash(Buffer.concat([metadataBuf, sigBuf]))
|
|
||||||
.subarray(0, 16)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMsgHash(nativeMsg) {
|
/**
|
||||||
const msgHashBuf = getMsgHashBuf(nativeMsg)
|
* @param {Msg | string} x
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function getMsgHash(x) {
|
||||||
|
if (typeof x === 'string') {
|
||||||
|
if (x.startsWith('ppppp:message/v1/')) {
|
||||||
|
const msgUri = x
|
||||||
|
const parts = msgUri.split('/')
|
||||||
|
return parts[parts.length - 1]
|
||||||
|
} else {
|
||||||
|
const msgHash = x
|
||||||
|
return msgHash
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const msg = x
|
||||||
|
const msgHashBuf = getMsgHashBuf(msg)
|
||||||
return base58.encode(msgHashBuf)
|
return base58.encode(msgHashBuf)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMsgId(nativeMsg) {
|
/**
|
||||||
const author = nativeMsg.metadata.author
|
* @param {Msg} msg
|
||||||
const type = nativeMsg.metadata.type
|
* @returns {string}
|
||||||
const msgHash = getMsgHash(nativeMsg)
|
*/
|
||||||
return `ssb:message/dag/${author}/${type}/${msgHash}`
|
function getMsgId(msg) {
|
||||||
|
const { who, type } = msg.metadata
|
||||||
|
const msgHash = getMsgHash(msg)
|
||||||
|
if (type) {
|
||||||
|
return `ppppp:message/v1/${who}/${type}/${msgHash}`
|
||||||
|
} else {
|
||||||
|
return `ppppp:message/v1/${who}/${msgHash}`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { getMsgId, getMsgHash }
|
module.exports = { getMsgId, getMsgHash }
|
||||||
|
|
|
@ -5,12 +5,7 @@
|
||||||
const stringify = require('fast-json-stable-stringify')
|
const stringify = require('fast-json-stable-stringify')
|
||||||
const ed25519 = require('ssb-keys/sodium')
|
const ed25519 = require('ssb-keys/sodium')
|
||||||
const base58 = require('bs58')
|
const base58 = require('bs58')
|
||||||
const {
|
const { stripAuthor, stripMsgKey } = require('./strip')
|
||||||
stripAuthor,
|
|
||||||
stripMsgKey,
|
|
||||||
unstripMsgKey,
|
|
||||||
unstripAuthor,
|
|
||||||
} = require('./strip')
|
|
||||||
const { getMsgId, getMsgHash } = require('./get-msg-id')
|
const { getMsgId, getMsgHash } = require('./get-msg-id')
|
||||||
const representContent = require('./represent-content')
|
const representContent = require('./represent-content')
|
||||||
const {
|
const {
|
||||||
|
@ -22,18 +17,32 @@ const {
|
||||||
validateOOOBatch,
|
validateOOOBatch,
|
||||||
} = require('./validation')
|
} = require('./validation')
|
||||||
|
|
||||||
const name = 'dag'
|
/**
|
||||||
const encodings = ['js']
|
* @typedef {Object} Msg
|
||||||
|
* @property {*} content
|
||||||
|
* @property {Object} metadata
|
||||||
|
* @property {number} metadata.depth
|
||||||
|
* @property {Array<string>} metadata.prev
|
||||||
|
* @property {string} metadata.proof
|
||||||
|
* @property {number} metadata.size
|
||||||
|
* @property {string=} metadata.type
|
||||||
|
* @property {string} metadata.who
|
||||||
|
* @property {number=} metadata.when
|
||||||
|
* @property {string} sig
|
||||||
|
*/
|
||||||
|
|
||||||
function getFeedId(nativeMsg) {
|
/**
|
||||||
return nativeMsg.metadata.author + nativeMsg.metadata.type
|
* @param {Msg} msg
|
||||||
|
*/
|
||||||
|
function getFeedId(msg) {
|
||||||
|
if (msg.metadata.type) {
|
||||||
|
return `ppppp:feed/v1/${msg.metadata.who}/${msg.metadata.type}`
|
||||||
|
} else {
|
||||||
|
return `ppppp:feed/v1/${msg.metadata.who}`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSequence(nativeMsg) {
|
function isMsg(x) {
|
||||||
throw new Error('getSequence not supported for dagfeed')
|
|
||||||
}
|
|
||||||
|
|
||||||
function isNativeMsg(x) {
|
|
||||||
return (
|
return (
|
||||||
typeof x === 'object' &&
|
typeof x === 'object' &&
|
||||||
!!x &&
|
!!x &&
|
||||||
|
@ -44,119 +53,92 @@ function isNativeMsg(x) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAuthor(author) {
|
function isFeedId(author) {
|
||||||
if (typeof author !== 'string') return false
|
if (typeof author !== 'string') return false
|
||||||
return author.startsWith('ssb:feed/dag/')
|
return author.startsWith('ppppp:feed/v1/')
|
||||||
}
|
}
|
||||||
|
|
||||||
function toPlaintextBuffer(opts) {
|
function toPlaintextBuffer(opts) {
|
||||||
return Buffer.from(stringify(opts.content), 'utf8')
|
return Buffer.from(stringify(opts.content), 'utf8')
|
||||||
}
|
}
|
||||||
|
|
||||||
function newNativeMsg(opts) {
|
function calculateDepth(prev) {
|
||||||
|
let max = -1;
|
||||||
|
for (const p of prev) {
|
||||||
|
if (p.metadata.depth > max) {
|
||||||
|
max = p.metadata.depth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return max + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function summarizePrev(prev) {
|
||||||
|
return Array.from(prev).map(getMsgHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
function prevalidatePrev(prev) {
|
||||||
|
if (prev && !prev[Symbol.iterator]) {
|
||||||
|
// prettier-ignore
|
||||||
|
throw new Error('opts.prev must be an iterator, but got ' + typeof prev)
|
||||||
|
}
|
||||||
|
for (const p of prev) {
|
||||||
|
if (!p.metadata) {
|
||||||
|
throw new Error('opts.prev must contain messages, but got ' + typeof p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {*} opts
|
||||||
|
* @returns {Msg}
|
||||||
|
*/
|
||||||
|
function create(opts) {
|
||||||
let err
|
let err
|
||||||
if ((err = validateType(opts.type))) throw err
|
if ((err = validateType(opts.type))) throw err
|
||||||
if (opts.previous && !Array.isArray(opts.previous)) {
|
prevalidatePrev(opts.prev)
|
||||||
// prettier-ignore
|
|
||||||
throw new Error('opts.previous must be an array, but got ' + typeof opts.previous)
|
|
||||||
}
|
|
||||||
|
|
||||||
const [contentHash, contentSize] = representContent(opts.content)
|
const [proof, size] = representContent(opts.content)
|
||||||
const nativeMsg = {
|
const depth = calculateDepth(opts.prev)
|
||||||
metadata: {
|
const msg = {
|
||||||
author: stripAuthor(opts.keys.id),
|
|
||||||
type: opts.type,
|
|
||||||
previous: (opts.previous ?? []).map(stripMsgKey),
|
|
||||||
timestamp: +opts.timestamp,
|
|
||||||
contentHash,
|
|
||||||
contentSize,
|
|
||||||
},
|
|
||||||
content: opts.content,
|
content: opts.content,
|
||||||
signature: '',
|
|
||||||
}
|
|
||||||
if ((err = validateContent(nativeMsg))) throw err
|
|
||||||
|
|
||||||
const metadataBuf = Buffer.from(stringify(nativeMsg.metadata), 'utf8')
|
|
||||||
// FIXME: this should allow using hmacKey
|
|
||||||
const privateKey = Buffer.from(opts.keys.private, 'base64')
|
|
||||||
const signature = ed25519.sign(privateKey, metadataBuf)
|
|
||||||
nativeMsg.signature = base58.encode(signature)
|
|
||||||
return nativeMsg
|
|
||||||
}
|
|
||||||
|
|
||||||
function fromNativeMsg(nativeMsg, encoding = 'js') {
|
|
||||||
if (encoding === 'js') {
|
|
||||||
const msgVal = {
|
|
||||||
// traditional:
|
|
||||||
previous: nativeMsg.metadata.previous.map((id) =>
|
|
||||||
unstripMsgKey(nativeMsg, id)
|
|
||||||
),
|
|
||||||
sequence: 0,
|
|
||||||
author: unstripAuthor(nativeMsg),
|
|
||||||
timestamp: nativeMsg.metadata.timestamp,
|
|
||||||
content: nativeMsg.content,
|
|
||||||
signature: nativeMsg.signature,
|
|
||||||
// unusual:
|
|
||||||
contentHash: nativeMsg.metadata.contentHash,
|
|
||||||
contentSize: nativeMsg.metadata.contentSize,
|
|
||||||
type: nativeMsg.metadata.type,
|
|
||||||
}
|
|
||||||
if (typeof msgVal.content === 'object') {
|
|
||||||
msgVal.content.type = nativeMsg.metadata.type
|
|
||||||
}
|
|
||||||
return msgVal
|
|
||||||
} else {
|
|
||||||
// prettier-ignore
|
|
||||||
throw new Error(`Feed format "${name}" does not support encoding "${encoding}"`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function fromDecryptedNativeMsg(plaintextBuf, nativeMsg, encoding = 'js') {
|
|
||||||
if (encoding === 'js') {
|
|
||||||
const msgVal = fromNativeMsg(nativeMsg, 'js')
|
|
||||||
const content = JSON.parse(plaintextBuf.toString('utf8'))
|
|
||||||
msgVal.content = content
|
|
||||||
msgVal.content.type = nativeMsg.metadata.type
|
|
||||||
return msgVal
|
|
||||||
} else {
|
|
||||||
// prettier-ignore
|
|
||||||
throw new Error(`Feed format "${name}" does not support encoding "${encoding}"`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toNativeMsg(msgVal, encoding = 'js') {
|
|
||||||
if (encoding === 'js') {
|
|
||||||
return {
|
|
||||||
metadata: {
|
metadata: {
|
||||||
author: stripAuthor(msgVal.author),
|
depth,
|
||||||
type: msgVal.type ?? '',
|
prev: summarizePrev(opts.prev),
|
||||||
previous: (msgVal.previous ?? []).map(stripMsgKey),
|
proof,
|
||||||
timestamp: msgVal.timestamp,
|
size,
|
||||||
contentHash: msgVal.contentHash,
|
type: opts.type,
|
||||||
contentSize: msgVal.contentSize,
|
who: stripAuthor(opts.keys.id),
|
||||||
|
when: +opts.when,
|
||||||
},
|
},
|
||||||
content: msgVal.content,
|
sig: '',
|
||||||
signature: msgVal.signature,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// prettier-ignore
|
|
||||||
throw new Error(`Feed format "${name}" does not support encoding "${encoding}"`)
|
|
||||||
}
|
}
|
||||||
|
if ((err = validateContent(msg))) throw err
|
||||||
|
|
||||||
|
const privateKey = Buffer.from(opts.keys.private, 'base64')
|
||||||
|
const metadataBuf = Buffer.from(stringify(msg.metadata), 'utf8')
|
||||||
|
// TODO: when signing, what's the point of a customizable hmac?
|
||||||
|
const sigBuf = ed25519.sign(privateKey, metadataBuf)
|
||||||
|
msg.sig = base58.encode(sigBuf)
|
||||||
|
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Buffer} plaintextBuf
|
||||||
|
* @param {Msg} msg
|
||||||
|
* @returns {Msg}
|
||||||
|
*/
|
||||||
|
function fromPlaintextBuffer(plaintextBuf, msg) {
|
||||||
|
return { ...msg, content: JSON.parse(plaintextBuf.toString('utf-8')) }
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name,
|
|
||||||
encodings,
|
|
||||||
getMsgId,
|
getMsgId,
|
||||||
getFeedId,
|
getFeedId,
|
||||||
getSequence,
|
isFeedId,
|
||||||
isAuthor,
|
isMsg,
|
||||||
isNativeMsg,
|
create,
|
||||||
toPlaintextBuffer,
|
toPlaintextBuffer,
|
||||||
newNativeMsg,
|
fromPlaintextBuffer,
|
||||||
fromNativeMsg,
|
|
||||||
fromDecryptedNativeMsg,
|
|
||||||
toNativeMsg,
|
|
||||||
validate,
|
validate,
|
||||||
validateOOO,
|
validateOOO,
|
||||||
validateBatch,
|
validateBatch,
|
||||||
|
|
|
@ -2,6 +2,10 @@ const blake3 = require('blake3')
|
||||||
const base58 = require('bs58')
|
const base58 = require('bs58')
|
||||||
const stringify = require('fast-json-stable-stringify')
|
const stringify = require('fast-json-stable-stringify')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} content
|
||||||
|
* @returns {[string, number]}
|
||||||
|
*/
|
||||||
function representContent(content) {
|
function representContent(content) {
|
||||||
const contentBuf = Buffer.from(stringify(content), 'utf8')
|
const contentBuf = Buffer.from(stringify(content), 'utf8')
|
||||||
const hash = base58.encode(blake3.hash(contentBuf).subarray(0, 16))
|
const hash = base58.encode(blake3.hash(contentBuf).subarray(0, 16))
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
|
const { getMsgHash } = require('./get-msg-id')
|
||||||
|
|
||||||
function stripMsgKey(msgKey) {
|
function stripMsgKey(msgKey) {
|
||||||
if (typeof msgKey === 'object') return stripMsgKey(msgKey.key)
|
if (typeof msgKey === 'object') {
|
||||||
if (msgKey.startsWith('ssb:message/dag/')) {
|
if (msgKey.key) return stripMsgKey(msgKey.key)
|
||||||
|
else return getMsgHash(msgKey)
|
||||||
|
}
|
||||||
|
if (msgKey.startsWith('ppppp:message/v1/')) {
|
||||||
const parts = msgKey.split('/')
|
const parts = msgKey.split('/')
|
||||||
return parts[parts.length - 1]
|
return parts[parts.length - 1]
|
||||||
} else {
|
} else {
|
||||||
|
@ -8,24 +13,12 @@ function stripMsgKey(msgKey) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function unstripMsgKey(nativeMsg, msgId) {
|
|
||||||
const { author, type } = nativeMsg.metadata
|
|
||||||
return `ssb:message/dag/${author}/${type}/${msgId}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripAuthor(id) {
|
function stripAuthor(id) {
|
||||||
const withoutPrefix = id.replace('ssb:feed/dag/', '')
|
const withoutPrefix = id.replace('ppppp:feed/v1/', '')
|
||||||
return withoutPrefix.split('/')[0]
|
return withoutPrefix.split('/')[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
function unstripAuthor(nativeMsg) {
|
|
||||||
const { author, type } = nativeMsg.metadata
|
|
||||||
return `ssb:feed/dag/${author}/${type}`
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
stripMsgKey,
|
stripMsgKey,
|
||||||
unstripMsgKey,
|
|
||||||
stripAuthor,
|
stripAuthor,
|
||||||
unstripAuthor,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,132 +1,122 @@
|
||||||
const base58 = require('bs58')
|
const base58 = require('bs58')
|
||||||
const ed25519 = require('ssb-keys/sodium')
|
const ed25519 = require('ssb-keys/sodium')
|
||||||
const stringify = require('fast-json-stable-stringify')
|
const stringify = require('fast-json-stable-stringify')
|
||||||
const { stripMsgKey } = require('./strip')
|
|
||||||
const { getMsgHash } = require('./get-msg-id')
|
|
||||||
|
|
||||||
function validateShape(nativeMsg) {
|
function validateShape(msg) {
|
||||||
if (!nativeMsg || typeof nativeMsg !== 'object') {
|
if (!msg || typeof msg !== 'object') {
|
||||||
return new Error('invalid message: not a dag msg')
|
return new Error('invalid message: not an object')
|
||||||
}
|
}
|
||||||
if (!nativeMsg.metadata || typeof nativeMsg.metadata !== 'object') {
|
if (!msg.metadata || typeof msg.metadata !== 'object') {
|
||||||
return new Error('invalid message: must have metadata')
|
return new Error('invalid message: must have metadata')
|
||||||
}
|
}
|
||||||
if (typeof nativeMsg.metadata.author === 'undefined') {
|
if (typeof msg.metadata.who === 'undefined') {
|
||||||
return new Error('invalid message: must have metadata.author')
|
return new Error('invalid message: must have metadata.who')
|
||||||
}
|
}
|
||||||
if (typeof nativeMsg.metadata.type === 'undefined') {
|
if (typeof msg.metadata.depth === 'undefined') {
|
||||||
return new Error('invalid message: must have metadata.sequence')
|
return new Error('invalid message: must have metadata.depth')
|
||||||
}
|
}
|
||||||
if (typeof nativeMsg.metadata.previous === 'undefined') {
|
if (typeof msg.metadata.prev === 'undefined') {
|
||||||
return new Error('invalid message: must have metadata.previous')
|
return new Error('invalid message: must have metadata.prev')
|
||||||
}
|
}
|
||||||
if (typeof nativeMsg.metadata.timestamp === 'undefined') {
|
if (typeof msg.metadata.proof === 'undefined') {
|
||||||
return new Error('invalid message: must have metadata.timestamp')
|
return new Error('invalid message: must have metadata.proof')
|
||||||
}
|
}
|
||||||
if (typeof nativeMsg.metadata.contentHash === 'undefined') {
|
if (typeof msg.metadata.size === 'undefined') {
|
||||||
return new Error('invalid message: must have metadata.contentHash')
|
return new Error('invalid message: must have metadata.size')
|
||||||
}
|
}
|
||||||
if (typeof nativeMsg.metadata.contentSize === 'undefined') {
|
if (typeof msg.content === 'undefined') {
|
||||||
return new Error('invalid message: must have metadata.contentSize')
|
|
||||||
}
|
|
||||||
if (typeof nativeMsg.content === 'undefined') {
|
|
||||||
return new Error('invalid message: must have content')
|
return new Error('invalid message: must have content')
|
||||||
}
|
}
|
||||||
if (typeof nativeMsg.signature === 'undefined') {
|
if (typeof msg.sig === 'undefined') {
|
||||||
return new Error('invalid message: must have signature')
|
return new Error('invalid message: must have sig')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateAuthor(nativeMsg) {
|
function validateWho(msg) {
|
||||||
try {
|
try {
|
||||||
base58.decode(nativeMsg.metadata.author)
|
base58.decode(msg.metadata.who)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return new Error('invalid message: must have author as base58 string')
|
return new Error('invalid message: must have "who" as base58 string')
|
||||||
}
|
}
|
||||||
|
// FIXME: if there are prev, then `who` must match
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateSignature(nativeMsg, hmacKey) {
|
function validateSignature(msg) {
|
||||||
const { signature } = nativeMsg
|
const { sig } = msg
|
||||||
if (typeof signature !== 'string') {
|
if (typeof sig !== 'string') {
|
||||||
return new Error('invalid message: must have signature as a string')
|
return new Error('invalid message: must have sig as a string')
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
base58.decode(signature)
|
base58.decode(sig)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return new Error('invalid message: signature must be a base58 string')
|
return new Error('invalid message: sig must be a base58 string')
|
||||||
}
|
}
|
||||||
const signatureBuf = Buffer.from(base58.decode(signature))
|
const sigBuf = Buffer.from(base58.decode(sig))
|
||||||
if (signatureBuf.length !== 64) {
|
if (sigBuf.length !== 64) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return new Error('invalid message: signature should be 64 bytes but was ' + signatureBuf.length + ', on feed: ' + nativeMsg.metadata.author);
|
return new Error('invalid message: sig should be 64 bytes but was ' + sigBuf.length + ', on feed: ' + msg.metadata.who);
|
||||||
}
|
}
|
||||||
|
|
||||||
const publicKeyBuf = Buffer.from(base58.decode(nativeMsg.metadata.author))
|
const publicKeyBuf = Buffer.from(base58.decode(msg.metadata.who))
|
||||||
const signableBuf = Buffer.from(stringify(nativeMsg.metadata), 'utf8')
|
const signableBuf = Buffer.from(stringify(msg.metadata), 'utf8')
|
||||||
const verified = ed25519.verify(publicKeyBuf, signatureBuf, signableBuf)
|
const verified = ed25519.verify(publicKeyBuf, sigBuf, signableBuf)
|
||||||
if (!verified) {
|
if (!verified) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return new Error('invalid message: signature does not match, on feed: ' + nativeMsg.metadata.author);
|
return new Error('invalid message: sig does not match, on feed: ' + msg.metadata.who);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validatePrevious(nativeMsg, existingNativeMsgs) {
|
function validatePrev(msg, existingMsgs) {
|
||||||
if (!Array.isArray(nativeMsg.metadata.previous)) {
|
if (!msg.metadata.prev || !msg.metadata.prev[Symbol.iterator]) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return new Error('invalid message: previous must be an array, on feed: ' + nativeMsg.metadata.author);
|
return new Error('invalid message: prev must be an iterator, on feed: ' + msg.metadata.who);
|
||||||
}
|
}
|
||||||
for (const prevId of nativeMsg.metadata.previous) {
|
for (const p of msg.metadata.prev) {
|
||||||
if (typeof prevId !== 'string') {
|
if (typeof p !== 'string') {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return new Error('invalid message: previous must contain strings but found ' + prevId + ', on feed: ' + nativeMsg.metadata.author);
|
return new Error('invalid message: prev must contain strings but found ' + p + ', on feed: ' + msg.metadata.who);
|
||||||
}
|
}
|
||||||
if (prevId.startsWith('ssb:')) {
|
if (p.startsWith('ppppp:')) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return new Error('invalid message: previous must not contain SSB URIs, on feed: ' + nativeMsg.metadata.author);
|
return new Error('invalid message: prev must not contain URIs, on feed: ' + msg.metadata.who);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existingNativeMsgs instanceof Set) {
|
if (!existingMsgs.has(p)) {
|
||||||
if (!existingNativeMsgs.has(prevId)) {
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return new Error('invalid message: previous ' + prevId + ' is not a known message ID, on feed: ' + nativeMsg.metadata.author);
|
return new Error('invalid message: prev ' + p + ' is not locally known, on feed: ' + msg.metadata.who);
|
||||||
}
|
}
|
||||||
continue
|
const existingMsg = existingMsgs.get(p)
|
||||||
} else {
|
|
||||||
let found = false
|
if (existingMsg.metadata.who !== msg.metadata.who) {
|
||||||
for (const nmsg of existingNativeMsgs) {
|
|
||||||
const existingId = nmsg.key
|
|
||||||
? stripMsgKey(nmsg.key)
|
|
||||||
: typeof nmsg === 'string'
|
|
||||||
? stripMsgKey(nmsg)
|
|
||||||
: getMsgHash(nmsg)
|
|
||||||
if (existingId === prevId) {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found) {
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return new Error('invalid message: previous ' + prevId + ' is not a known message ID, on feed: ' + nativeMsg.metadata.author);
|
return new Error('invalid message: prev ' + p + ' is not from the same who, on feed: ' + msg.metadata.who);
|
||||||
}
|
}
|
||||||
|
if (existingMsg.metadata.type !== msg.metadata.type) {
|
||||||
|
// prettier-ignore
|
||||||
|
return new Error('invalid message: prev ' + p + ' is not from the same type, on feed: ' + msg.metadata.who);
|
||||||
|
}
|
||||||
|
if (existingMsg.metadata.depth >= msg.metadata.depth) {
|
||||||
|
// prettier-ignore
|
||||||
|
return new Error('invalid message: depth of prev ' + p + ' is not lower, on feed: ' + msg.metadata.who);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateFirstPrevious(nativeMsg) {
|
function validateFirstPrev(msg) {
|
||||||
if (!Array.isArray(nativeMsg.metadata.previous)) {
|
if (!Array.isArray(msg.metadata.prev)) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return new Error('invalid message: previous must be an array, on feed: ' + nativeMsg.metadata.author);
|
return new Error('invalid message: prev must be an array, on feed: ' + msg.metadata.who);
|
||||||
}
|
}
|
||||||
if (nativeMsg.metadata.previous.length !== 0) {
|
if (msg.metadata.prev.length !== 0) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return new Error('initial message: previous must be an empty array, on feed: ' + nativeMsg.metadata.author);
|
return new Error('invalid message: prev of 1st msg must be an empty array, on feed: ' + msg.metadata.who);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateTimestamp(nativeMsg) {
|
function validateWhen(msg) {
|
||||||
if (typeof nativeMsg.metadata.timestamp !== 'number') {
|
if (msg.metadata.when && typeof msg.metadata.when !== 'number') {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return new Error('initial message must have timestamp, on feed: ' + nativeMsg.metadata.author);
|
return new Error('invalid message: `when` is not a number, on feed: ' + msg.metadata.who);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,8 +139,10 @@ function validateType(type) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateContent(nativeMsg) {
|
function validateContent(msg) {
|
||||||
const { content } = nativeMsg
|
// FIXME: if content exists, check it against `proof` and `size`
|
||||||
|
// FIXME: if content does not exist, do nothing
|
||||||
|
const { content } = msg
|
||||||
if (!content) {
|
if (!content) {
|
||||||
return new Error('invalid message: must have content')
|
return new Error('invalid message: must have content')
|
||||||
}
|
}
|
||||||
|
@ -159,51 +151,24 @@ function validateContent(nativeMsg) {
|
||||||
}
|
}
|
||||||
if (typeof content !== 'object' && typeof content !== 'string') {
|
if (typeof content !== 'object' && typeof content !== 'string') {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return new Error('invalid message: content must be an object or string, on feed: ' + nativeMsg.metadata.author);
|
return new Error('invalid message: content must be an object or string, on feed: ' + msg.metadata.who);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateHmac(hmacKey) {
|
// FIXME: validateDepth should be +1 of the max of prev depth
|
||||||
if (!hmacKey) return
|
|
||||||
if (typeof hmacKey !== 'string' && !Buffer.isBuffer(hmacKey)) {
|
|
||||||
return new Error('invalid hmac key: must be a string or buffer')
|
|
||||||
}
|
|
||||||
const bytes = Buffer.isBuffer(hmacKey)
|
|
||||||
? hmacKey
|
|
||||||
: Buffer.from(hmacKey, 'base64')
|
|
||||||
|
|
||||||
if (typeof hmacKey === 'string' && bytes.toString('base64') !== hmacKey) {
|
function validateSync(msg, existingMsgs) {
|
||||||
return new Error('invalid hmac')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bytes.length !== 32) {
|
|
||||||
return new Error('invalid hmac, it should have 32 bytes')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function emptyExisting(existingNativeMsgs) {
|
|
||||||
if (existingNativeMsgs instanceof Set) {
|
|
||||||
return existingNativeMsgs.size === 0
|
|
||||||
} else if (Array.isArray(existingNativeMsgs)) {
|
|
||||||
return existingNativeMsgs.length === 0
|
|
||||||
} else {
|
|
||||||
return !existingNativeMsgs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateSync(nativeMsg, existingNativeMsgs, hmacKey) {
|
|
||||||
let err
|
let err
|
||||||
if ((err = validateShape(nativeMsg))) return err
|
if ((err = validateShape(msg))) return err
|
||||||
if ((err = validateHmac(hmacKey))) return err
|
if ((err = validateWho(msg))) return err
|
||||||
if ((err = validateAuthor(nativeMsg))) return err
|
if ((err = validateWhen(msg))) return err
|
||||||
if ((err = validateTimestamp(nativeMsg))) return err
|
if (msg.metadata.depth === 0) {
|
||||||
if (emptyExisting(existingNativeMsgs)) {
|
if ((err = validateFirstPrev(msg))) return err
|
||||||
if ((err = validateFirstPrevious(nativeMsg))) return err
|
|
||||||
} else {
|
} else {
|
||||||
if ((err = validatePrevious(nativeMsg, existingNativeMsgs))) return err
|
if ((err = validatePrev(msg, existingMsgs))) return err
|
||||||
}
|
}
|
||||||
if ((err = validateContent(nativeMsg))) return err
|
if ((err = validateContent(msg))) return err
|
||||||
if ((err = validateSignature(nativeMsg, hmacKey))) return err
|
if ((err = validateSignature(msg))) return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// function validateOOOSync(nativeMsg, hmacKey) {
|
// function validateOOOSync(nativeMsg, hmacKey) {
|
||||||
|
@ -212,16 +177,15 @@ function validateSync(nativeMsg, existingNativeMsgs, hmacKey) {
|
||||||
// if ((err = validateHmac(hmacKey))) return err
|
// if ((err = validateHmac(hmacKey))) return err
|
||||||
// if ((err = validateAuthor(nativeMsg))) return err
|
// if ((err = validateAuthor(nativeMsg))) return err
|
||||||
// if ((err = validateHash(nativeMsg))) return err
|
// if ((err = validateHash(nativeMsg))) return err
|
||||||
// if ((err = validateTimestamp(nativeMsg))) return err
|
|
||||||
// if ((err = validateOrder(nativeMsg))) return err
|
// if ((err = validateOrder(nativeMsg))) return err
|
||||||
// if ((err = validateContent(nativeMsg))) return err
|
// if ((err = validateContent(nativeMsg))) return err
|
||||||
// if ((err = validateAsJSON(nativeMsg))) return err
|
// if ((err = validateAsJSON(nativeMsg))) return err
|
||||||
// if ((err = validateSignature(nativeMsg, hmacKey))) return err
|
// if ((err = validateSignature(nativeMsg, hmacKey))) return err
|
||||||
// }
|
// }
|
||||||
|
|
||||||
function validate(nativeMsg, prevNativeMsg, hmacKey, cb) {
|
function validate(msg, existingMsgs, cb) {
|
||||||
let err
|
let err
|
||||||
if ((err = validateSync(nativeMsg, prevNativeMsg, hmacKey))) {
|
if ((err = validateSync(msg, existingMsgs))) {
|
||||||
return cb(err)
|
return cb(err)
|
||||||
}
|
}
|
||||||
cb()
|
cb()
|
||||||
|
|
362
lib/plugin.js
362
lib/plugin.js
|
@ -3,80 +3,88 @@ const push = require('push-stream')
|
||||||
const AAOL = require('async-append-only-log')
|
const AAOL = require('async-append-only-log')
|
||||||
const promisify = require('promisify-4loc')
|
const promisify = require('promisify-4loc')
|
||||||
const Obz = require('obz')
|
const Obz = require('obz')
|
||||||
|
const FeedV1 = require('./feed-v1')
|
||||||
const { ReadyGate } = require('./utils')
|
const { ReadyGate } = require('./utils')
|
||||||
const { decrypt, reEncrypt } = require('./encryption')
|
const { decrypt } = require('./encryption')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('./feed-v1').Msg} Msg
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} RecDeleted
|
||||||
|
* @property {never} id
|
||||||
|
* @property {never} msg
|
||||||
|
* @property {never} received
|
||||||
|
* @property {Object} misc
|
||||||
|
* @property {number} misc.offset
|
||||||
|
* @property {number} misc.size
|
||||||
|
* @property {number} misc.seq
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} RecPresent
|
||||||
|
* @property {string} id
|
||||||
|
* @property {Msg} msg
|
||||||
|
* @property {number} received
|
||||||
|
* @property {Object} misc
|
||||||
|
* @property {number} misc.offset
|
||||||
|
* @property {number} misc.size
|
||||||
|
* @property {number} misc.seq
|
||||||
|
* @property {boolean=} misc.private
|
||||||
|
* @property {Object=} misc.originalContent
|
||||||
|
* @property {string=} misc.encryptionFormat
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {RecPresent | RecDeleted} Rec
|
||||||
|
*/
|
||||||
|
|
||||||
exports.name = 'db'
|
exports.name = 'db'
|
||||||
|
|
||||||
exports.init = function initMemDB(ssb, config) {
|
exports.init = function initDB(peer, config) {
|
||||||
const hmacKey = null
|
/** @type {Array<Rec>} */
|
||||||
const msgs = []
|
const recs = []
|
||||||
const feedFormats = new Map()
|
|
||||||
const encryptionFormats = new Map()
|
const encryptionFormats = new Map()
|
||||||
const onMsgAdded = Obz()
|
const onRecordAdded = Obz()
|
||||||
|
|
||||||
const latestMsgPerFeed = {
|
const msgsPerFeed = {
|
||||||
_map: new Map(), // feedId => nativeMsg
|
_mapAll: new Map(), // who => Set<Msg>
|
||||||
preupdateFromKVT(kvtf, i) {
|
_mapTips: new Map(), // who => Set<Msg>
|
||||||
const feedId = kvtf.feed ?? kvtf.value.author
|
_byHash: new Map(), // msgId => Msg // TODO: optimize space usage of this??
|
||||||
this._map.set(feedId, i)
|
update(msg, msgId) {
|
||||||
},
|
const msgHash = FeedV1.getMsgHash(msgId ?? msg)
|
||||||
commitAllPreupdates() {
|
const feedId = FeedV1.getFeedId(msg)
|
||||||
for (const i of this._map.values()) {
|
const setAll = this._mapAll.get(feedId) ?? new Set()
|
||||||
if (typeof i === 'number') {
|
const setTips = this._mapTips.get(feedId) ?? new Set()
|
||||||
this.updateFromKVT(msgs[i])
|
for (const p of msg.metadata.prev) {
|
||||||
}
|
const prevMsg = this._byHash.get(p)
|
||||||
|
setTips.delete(prevMsg)
|
||||||
}
|
}
|
||||||
|
setAll.add(msg)
|
||||||
|
setTips.add(msg)
|
||||||
|
this._mapTips.set(feedId, setTips)
|
||||||
|
this._mapAll.set(feedId, setAll)
|
||||||
|
this._byHash.set(msgHash, msg)
|
||||||
},
|
},
|
||||||
updateFromKVT(kvtf) {
|
getAll() {
|
||||||
const feedId = kvtf.feed ?? kvtf.value.author
|
return this._byHash
|
||||||
const feedFormat = findFeedFormatForAuthor(feedId)
|
|
||||||
if (!feedFormat) {
|
|
||||||
console.warn('No feed format installed understands ' + feedId)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const msg = reEncrypt(kvtf)
|
|
||||||
const nativeMsg = feedFormat.toNativeMsg(msg.value, 'js')
|
|
||||||
this._map.set(feedId, nativeMsg)
|
|
||||||
},
|
},
|
||||||
update(feedId, nativeMsg) {
|
getTips(feedId) {
|
||||||
this._map.set(feedId, nativeMsg)
|
return this._mapTips.get(feedId) ?? []
|
||||||
},
|
},
|
||||||
get(feedId) {
|
deleteMsg(msg) {
|
||||||
return this._map.get(feedId) ?? null
|
const feedId = FeedV1.getFeedId(msg)
|
||||||
},
|
const msgHash = FeedV1.getMsgHash(msg)
|
||||||
has(feedId) {
|
const setAll = this._mapAll.get(feedId)
|
||||||
return this._map.has(feedId)
|
setAll.delete(msg)
|
||||||
},
|
const setTips = this._mapTips.get(feedId)
|
||||||
getAsKV(feedId, feedFormat) {
|
setTips.delete(msg)
|
||||||
const nativeMsg = this._map.get(feedId)
|
this._byHash.delete(msgHash)
|
||||||
if (!nativeMsg) return null
|
|
||||||
const feedFormat2 = feedFormat ?? findFeedFormatForAuthor(feedId)
|
|
||||||
if (!feedFormat2) {
|
|
||||||
throw new Error('No feed format installed understands ' + feedId)
|
|
||||||
}
|
|
||||||
const key = feedFormat2.getMsgId(nativeMsg, 'js')
|
|
||||||
const value = feedFormat2.fromNativeMsg(nativeMsg, 'js')
|
|
||||||
return { key, value }
|
|
||||||
},
|
|
||||||
deleteKVT(kvtf) {
|
|
||||||
const feedId = kvtf.feed ?? kvtf.value.author
|
|
||||||
const nativeMsg = this._map.get(feedId)
|
|
||||||
if (!nativeMsg) return
|
|
||||||
const feedFormat = findFeedFormatForAuthor(feedId)
|
|
||||||
if (!feedFormat) {
|
|
||||||
console.warn('No feed format installed understands ' + feedId)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const msgId = feedFormat.getMsgId(nativeMsg, 'js')
|
|
||||||
if (msgId === kvtf.key) this._map.delete(feedId)
|
|
||||||
},
|
|
||||||
delete(feedId) {
|
|
||||||
this._map.delete(feedId)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const log = AAOL(path.join(config.path, 'memdb-log.bin'), {
|
const log = AAOL(path.join(config.path, 'db.bin'), {
|
||||||
cacheSize: 1,
|
cacheSize: 1,
|
||||||
blockSize: 64 * 1024,
|
blockSize: 64 * 1024,
|
||||||
codec: {
|
codec: {
|
||||||
|
@ -97,14 +105,14 @@ exports.init = function initMemDB(ssb, config) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
ssb.close.hook(function (fn, args) {
|
peer.close.hook(function (fn, args) {
|
||||||
log.close(() => {
|
log.close(() => {
|
||||||
fn.apply(this, args)
|
fn.apply(this, args)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const scannedLog = new ReadyGate()
|
const scannedLog = new ReadyGate()
|
||||||
// setTimeout to let ssb.db.* secret-stack become available
|
// setTimeout to let peer.db.* secret-stack become available
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
let i = -1
|
let i = -1
|
||||||
log.stream({ offsets: true, values: true, sizes: true }).pipe(
|
log.stream({ offsets: true, values: true, sizes: true }).pipe(
|
||||||
|
@ -113,59 +121,49 @@ exports.init = function initMemDB(ssb, config) {
|
||||||
i += 1
|
i += 1
|
||||||
if (!value) {
|
if (!value) {
|
||||||
// deleted record
|
// deleted record
|
||||||
msgs.push(null)
|
recs.push({ misc: { offset, size, seq: i } })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// TODO: for performance, dont decrypt on startup, instead decrypt on
|
// TODO: for performance, dont decrypt on startup, instead decrypt on
|
||||||
// demand, or decrypt in the background. Or then store the log with
|
// demand, or decrypt in the background. Or then store the log with
|
||||||
// decrypted msgs and only encrypt when moving it to the network.
|
// decrypted msgs and only encrypt when moving it to the network.
|
||||||
const msg = decrypt(value, ssb, config)
|
const rec = decrypt(value, peer, config)
|
||||||
msg.meta ??= {}
|
rec.misc ??= {}
|
||||||
msg.meta.offset = offset
|
rec.misc.offset = offset
|
||||||
msg.meta.size = size
|
rec.misc.size = size
|
||||||
msg.meta.seq = i
|
rec.misc.seq = i
|
||||||
msgs.push(msg)
|
recs.push(rec)
|
||||||
|
|
||||||
latestMsgPerFeed.preupdateFromKVT(msg, i)
|
msgsPerFeed.update(rec.msg)
|
||||||
},
|
},
|
||||||
function drainEnd(err) {
|
function drainEnd(err) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
if (err) throw new Error('Failed to initially scan the log', { cause: err });
|
if (err) throw new Error('Failed to initially scan the log', { cause: err });
|
||||||
latestMsgPerFeed.commitAllPreupdates()
|
|
||||||
scannedLog.setReady()
|
scannedLog.setReady()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
function logAppend(key, value, feedId, isOOO, cb) {
|
function logAppend(id, msg, feedId, isOOO, cb) {
|
||||||
const kvt = {
|
const rec = {
|
||||||
key,
|
id,
|
||||||
value,
|
msg,
|
||||||
timestamp: Date.now(),
|
received: Date.now(),
|
||||||
}
|
}
|
||||||
if (feedId !== value.author) kvt.feed = feedId
|
if (isOOO) rec.ooo = isOOO
|
||||||
if (isOOO) kvt.ooo = isOOO
|
log.append(rec, (err, newOffset) => {
|
||||||
log.append(kvt, (err, newOffset) => {
|
|
||||||
if (err) return cb(new Error('logAppend failed', { cause: err }))
|
if (err) return cb(new Error('logAppend failed', { cause: err }))
|
||||||
const offset = newOffset // latestOffset
|
const offset = newOffset // latestOffset
|
||||||
const size = Buffer.from(JSON.stringify(kvt), 'utf8').length
|
const size = Buffer.from(JSON.stringify(rec), 'utf8').length
|
||||||
const seq = msgs.length
|
const seq = recs.length
|
||||||
const kvtExposed = decrypt(kvt, ssb, config)
|
const recExposed = decrypt(rec, peer, config)
|
||||||
kvt.meta = kvtExposed.meta = { offset, size, seq }
|
rec.misc = recExposed.misc = { offset, size, seq }
|
||||||
msgs.push(kvtExposed)
|
recs.push(recExposed)
|
||||||
cb(null, kvt)
|
cb(null, rec)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function installFeedFormat(feedFormat) {
|
|
||||||
if (!feedFormat.encodings.includes('js')) {
|
|
||||||
// prettier-ignore
|
|
||||||
throw new Error(`Failed to install feed format "${feedFormat.name}" because it must support JS encoding`)
|
|
||||||
}
|
|
||||||
feedFormats.set(feedFormat.name, feedFormat)
|
|
||||||
}
|
|
||||||
|
|
||||||
function installEncryptionFormat(encryptionFormat) {
|
function installEncryptionFormat(encryptionFormat) {
|
||||||
if (encryptionFormat.setup) {
|
if (encryptionFormat.setup) {
|
||||||
const loaded = new ReadyGate()
|
const loaded = new ReadyGate()
|
||||||
|
@ -179,20 +177,6 @@ exports.init = function initMemDB(ssb, config) {
|
||||||
encryptionFormats.set(encryptionFormat.name, encryptionFormat)
|
encryptionFormats.set(encryptionFormat.name, encryptionFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
function findFeedFormatForAuthor(author) {
|
|
||||||
for (const feedFormat of feedFormats.values()) {
|
|
||||||
if (feedFormat.isAuthor(author)) return feedFormat
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
function findFeedFormatForNativeMsg(nativeMsg) {
|
|
||||||
for (const feedFormat of feedFormats.values()) {
|
|
||||||
if (feedFormat.isNativeMsg(nativeMsg)) return feedFormat
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
function findEncryptionFormatFor(ciphertextJS) {
|
function findEncryptionFormatFor(ciphertextJS) {
|
||||||
if (!ciphertextJS) return null
|
if (!ciphertextJS) return null
|
||||||
if (typeof ciphertextJS !== 'string') return null
|
if (typeof ciphertextJS !== 'string') return null
|
||||||
|
@ -201,90 +185,57 @@ exports.init = function initMemDB(ssb, config) {
|
||||||
return encryptionFormat
|
return encryptionFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
function add(nativeMsg, cb) {
|
function add(msg, cb) {
|
||||||
const feedFormat = findFeedFormatForNativeMsg(nativeMsg)
|
const feedId = FeedV1.getFeedId(msg)
|
||||||
if (!feedFormat) {
|
const existingMsgs = msgsPerFeed.getAll(feedId)
|
||||||
// prettier-ignore
|
|
||||||
return cb(new Error('add() failed because no installed feed format understands the native message'))
|
|
||||||
}
|
|
||||||
const feedId = feedFormat.getFeedId(nativeMsg)
|
|
||||||
const prevNativeMsg = latestMsgPerFeed.get(feedId)
|
|
||||||
|
|
||||||
if (prevNativeMsg) {
|
FeedV1.validate(msg, existingMsgs, validationCB)
|
||||||
feedFormat.validate(nativeMsg, prevNativeMsg, hmacKey, validationCB)
|
|
||||||
} else {
|
|
||||||
feedFormat.validateOOO(nativeMsg, hmacKey, validationCB)
|
|
||||||
}
|
|
||||||
|
|
||||||
function validationCB(err) {
|
function validationCB(err) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
if (err) return cb(new Error('add() failed validation for feed format ' + feedFormat.name, {cause: err}))
|
if (err) return cb(new Error('add() failed validation for feed format v1', {cause: err}))
|
||||||
const msgId = feedFormat.getMsgId(nativeMsg)
|
const msgId = FeedV1.getMsgId(msg)
|
||||||
const msgVal = feedFormat.fromNativeMsg(nativeMsg)
|
msgsPerFeed.update(msg, msgId)
|
||||||
latestMsgPerFeed.update(feedId, nativeMsg)
|
|
||||||
|
|
||||||
logAppend(msgId, msgVal, feedId, false, (err, kvt) => {
|
logAppend(msgId, msg, feedId, false, logAppendCB)
|
||||||
|
}
|
||||||
|
|
||||||
|
function logAppendCB(err, rec) {
|
||||||
if (err) return cb(new Error('add() failed in the log', { cause: err }))
|
if (err) return cb(new Error('add() failed in the log', { cause: err }))
|
||||||
|
onRecordAdded.set(rec)
|
||||||
onMsgAdded.set({
|
cb(null, rec)
|
||||||
kvt,
|
|
||||||
nativeMsg,
|
|
||||||
feedFormat: feedFormat.name,
|
|
||||||
})
|
|
||||||
cb(null, kvt)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function create(opts, cb) {
|
function create(opts, cb) {
|
||||||
const keys = opts.keys ?? config.keys
|
const keys = opts.keys ?? config.keys
|
||||||
|
|
||||||
const feedFormat = feedFormats.get(opts.feedFormat)
|
|
||||||
const encryptionFormat = encryptionFormats.get(opts.encryptionFormat)
|
const encryptionFormat = encryptionFormats.get(opts.encryptionFormat)
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
if (!feedFormat) return cb(new Error(`create() does not support feed format "${opts.feedFormat}"`))
|
|
||||||
// prettier-ignore
|
|
||||||
if (!feedFormat.isAuthor(keys.id)) return cb(new Error(`create() failed because keys.id ${keys.id} is not a valid author for feed format "${feedFormat.name}"`))
|
|
||||||
// prettier-ignore
|
|
||||||
if (opts.content.recps) {
|
if (opts.content.recps) {
|
||||||
if (!encryptionFormat) {
|
if (!encryptionFormat) {
|
||||||
return cb(new Error(`create() does not support encryption format "${opts.encryptionFormat}"`))
|
return cb(new Error(`create() does not support encryption format "${opts.encryptionFormat}"`))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!opts.content) return cb(new Error('create() requires a `content`'))
|
if (!opts.content) return cb(new Error('create() requires a `content`'))
|
||||||
|
if (!opts.type) return cb(new Error('create() requires a `type`'))
|
||||||
|
|
||||||
// Create full opts:
|
// Create full opts:
|
||||||
let provisionalNativeMsg
|
let tempMsg
|
||||||
try {
|
try {
|
||||||
provisionalNativeMsg = feedFormat.newNativeMsg({
|
tempMsg = FeedV1.create({ when: Date.now(), ...opts, prev: [], keys })
|
||||||
timestamp: Date.now(),
|
|
||||||
...opts,
|
|
||||||
previous: null,
|
|
||||||
keys,
|
|
||||||
})
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return cb(new Error('create() failed', { cause: err }))
|
return cb(new Error('create() failed', { cause: err }))
|
||||||
}
|
}
|
||||||
const feedId = feedFormat.getFeedId(provisionalNativeMsg)
|
const feedId = FeedV1.getFeedId(tempMsg)
|
||||||
const previous = latestMsgPerFeed.getAsKV(feedId, feedFormat)
|
const prev = msgsPerFeed.getTips(feedId)
|
||||||
const fullOpts = {
|
const fullOpts = { when: Date.now(), ...opts, prev, keys }
|
||||||
timestamp: Date.now(),
|
|
||||||
...opts,
|
|
||||||
previous,
|
|
||||||
keys,
|
|
||||||
hmacKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
// If opts ask for encryption, encrypt and put ciphertext in opts.content
|
// If opts ask for encryption, encrypt and put ciphertext in opts.content
|
||||||
const recps = fullOpts.content.recps
|
const recps = fullOpts.content.recps
|
||||||
if (Array.isArray(recps) && recps.length > 0) {
|
if (Array.isArray(recps) && recps.length > 0) {
|
||||||
const plaintext = feedFormat.toPlaintextBuffer(fullOpts)
|
const plaintext = FeedV1.toPlaintextBuffer(fullOpts)
|
||||||
const encryptOpts = {
|
const encryptOpts = { ...fullOpts, keys, recps, prev }
|
||||||
...fullOpts,
|
|
||||||
keys,
|
|
||||||
recps,
|
|
||||||
previous: previous ? previous.key : null,
|
|
||||||
}
|
|
||||||
let ciphertextBuf
|
let ciphertextBuf
|
||||||
try {
|
try {
|
||||||
ciphertextBuf = encryptionFormat.encrypt(plaintext, encryptOpts)
|
ciphertextBuf = encryptionFormat.encrypt(plaintext, encryptOpts)
|
||||||
|
@ -300,80 +251,59 @@ exports.init = function initMemDB(ssb, config) {
|
||||||
fullOpts.content = ciphertextBase64 + '.' + encryptionFormat.name
|
fullOpts.content = ciphertextBase64 + '.' + encryptionFormat.name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the native message:
|
// Create the actual message:
|
||||||
let nativeMsg
|
let msg
|
||||||
try {
|
try {
|
||||||
nativeMsg = feedFormat.newNativeMsg(fullOpts)
|
msg = FeedV1.create(fullOpts)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return cb(new Error('create() failed', { cause: err }))
|
return cb(new Error('create() failed', { cause: err }))
|
||||||
}
|
}
|
||||||
const msgId = feedFormat.getMsgId(nativeMsg)
|
const msgId = FeedV1.getMsgId(msg)
|
||||||
const msgVal = feedFormat.fromNativeMsg(nativeMsg, 'js')
|
msgsPerFeed.update(msg, msgId)
|
||||||
latestMsgPerFeed.update(feedId, nativeMsg)
|
|
||||||
|
|
||||||
// Encode the native message and append it to the log:
|
// Encode the native message and append it to the log:
|
||||||
logAppend(msgId, msgVal, feedId, false, (err, kvt) => {
|
logAppend(msgId, msg, feedId, false, (err, rec) => {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
if (err) return cb(new Error('create() failed to append the log', { cause: err }))
|
if (err) return cb(new Error('create() failed to append the log', { cause: err }))
|
||||||
onMsgAdded.set({
|
onRecordAdded.set(rec)
|
||||||
kvt,
|
cb(null, rec)
|
||||||
nativeMsg,
|
|
||||||
feedFormat: feedFormat.name,
|
|
||||||
})
|
|
||||||
cb(null, kvt)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function del(msgId, cb) {
|
function del(msgId, cb) {
|
||||||
const kvt = getKVT(msgId)
|
const rec = getRecord(msgId)
|
||||||
latestMsgPerFeed.deleteKVT(kvt)
|
msgsPerFeed.deleteMsg(rec.msg)
|
||||||
msgs[kvt.meta.seq] = null
|
const { offset, size, seq } = rec.misc
|
||||||
|
recs[rec.misc.seq] = { misc: { offset, size, seq } }
|
||||||
log.onDrain(() => {
|
log.onDrain(() => {
|
||||||
log.del(kvt.meta.offset, cb)
|
log.del(offset, cb)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterAsPullStream(fn) {
|
function* msgs() {
|
||||||
let i = 0
|
for (let i = 0; i < recs.length; i++) {
|
||||||
return function source(end, cb) {
|
const rec = recs[i]
|
||||||
if (end) return cb(end)
|
if (rec.msg) yield rec.msg
|
||||||
if (i >= msgs.length) return cb(true)
|
|
||||||
for (; i < msgs.length; i++) {
|
|
||||||
const msg = msgs[i]
|
|
||||||
if (msg && fn(msg, i, msgs)) {
|
|
||||||
i += 1
|
|
||||||
return cb(null, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cb(true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* filterAsIterator(fn) {
|
function* records() {
|
||||||
for (let i = 0; i < msgs.length; i++) {
|
for (let i = 0; i < recs.length; i++) {
|
||||||
const msg = msgs[i]
|
const rec = recs[i]
|
||||||
if (msg && fn(msg, i, msgs)) yield msg
|
if (rec) yield rec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterAsArray(fn) {
|
function getRecord(msgId) {
|
||||||
return msgs.filter(fn)
|
for (let i = 0; i < recs.length; i++) {
|
||||||
}
|
const rec = recs[i]
|
||||||
|
if (rec && rec.id === msgId) return rec
|
||||||
function forEach(fn) {
|
|
||||||
for (let i = 0; i < msgs.length; i++) if (msgs[i]) fn(msgs[i], i, msgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getKVT(msgKey) {
|
|
||||||
for (let i = 0; i < msgs.length; i++) {
|
|
||||||
const msg = msgs[i]
|
|
||||||
if (msg && msg.key === msgKey) return msg
|
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
function get(msgKey) {
|
function get(msgId) {
|
||||||
return getKVT(msgKey)?.value
|
return getRecord(msgId)?.msg
|
||||||
}
|
}
|
||||||
|
|
||||||
function loaded(cb) {
|
function loaded(cb) {
|
||||||
|
@ -383,22 +313,18 @@ exports.init = function initMemDB(ssb, config) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// public
|
// public
|
||||||
installFeedFormat,
|
|
||||||
installEncryptionFormat,
|
installEncryptionFormat,
|
||||||
loaded,
|
loaded,
|
||||||
add,
|
add,
|
||||||
create,
|
create,
|
||||||
del,
|
del,
|
||||||
onMsgAdded,
|
onRecordAdded,
|
||||||
filterAsPullStream,
|
msgs,
|
||||||
filterAsIterator,
|
records,
|
||||||
filterAsArray,
|
getRecord,
|
||||||
forEach,
|
|
||||||
getKVT,
|
|
||||||
get,
|
get,
|
||||||
|
|
||||||
// internal
|
// internal
|
||||||
findEncryptionFormatFor,
|
findEncryptionFormatFor,
|
||||||
findFeedFormatForAuthor,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,8 @@
|
||||||
"fast-json-stable-stringify": "^2.1.0",
|
"fast-json-stable-stringify": "^2.1.0",
|
||||||
"obz": "^1.1.0",
|
"obz": "^1.1.0",
|
||||||
"promisify-4loc": "^1.0.0",
|
"promisify-4loc": "^1.0.0",
|
||||||
"push-stream": "^11.2.0"
|
"push-stream": "^11.2.0",
|
||||||
|
"ssb-uri2": "^2.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"c8": "^7.11.0",
|
"c8": "^7.11.0",
|
||||||
|
|
113
test/add.test.js
113
test/add.test.js
|
@ -1,112 +1,35 @@
|
||||||
const test = require('tape')
|
const test = require('tape')
|
||||||
const ssbKeys = require('ssb-keys')
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const os = require('os')
|
const os = require('os')
|
||||||
const rimraf = require('rimraf')
|
const rimraf = require('rimraf')
|
||||||
const SecretStack = require('secret-stack')
|
const SecretStack = require('secret-stack')
|
||||||
const caps = require('ssb-caps')
|
const caps = require('ssb-caps')
|
||||||
const classic = require('ssb-classic/format')
|
const FeedV1 = require('../lib/feed-v1')
|
||||||
const p = require('util').promisify
|
const p = require('util').promisify
|
||||||
|
const { generateKeypair } = require('./util')
|
||||||
|
|
||||||
const DIR = path.join(os.tmpdir(), 'ppppp-db-add')
|
const DIR = path.join(os.tmpdir(), 'ppppp-db-add')
|
||||||
rimraf.sync(DIR)
|
rimraf.sync(DIR)
|
||||||
|
|
||||||
test('add() classic', async (t) => {
|
test('add()', async (t) => {
|
||||||
const ssb = SecretStack({ appKey: caps.shs })
|
const keys = generateKeypair('alice')
|
||||||
|
const peer = SecretStack({ appKey: caps.shs })
|
||||||
.use(require('../'))
|
.use(require('../'))
|
||||||
.use(require('ssb-classic'))
|
|
||||||
.use(require('ssb-box'))
|
.use(require('ssb-box'))
|
||||||
.call(null, {
|
.call(null, { keys, path: DIR })
|
||||||
keys: ssbKeys.generate('ed25519', 'alice'),
|
|
||||||
path: DIR,
|
await peer.db.loaded()
|
||||||
|
|
||||||
|
const inputMsg = FeedV1.create({
|
||||||
|
keys,
|
||||||
|
when: 1514517067954,
|
||||||
|
type: 'post',
|
||||||
|
content: { text: 'This is the first post!' },
|
||||||
|
prev: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
await ssb.db.loaded()
|
const rec = await p(peer.db.add)(inputMsg)
|
||||||
|
t.equal(rec.msg.content.text, 'This is the first post!')
|
||||||
|
|
||||||
const nativeMsg = classic.toNativeMsg(
|
await p(peer.close)(true)
|
||||||
{
|
|
||||||
previous: null,
|
|
||||||
author: '@FCX/tsDLpubCPKKfIrw4gc+SQkHcaD17s7GI6i/ziWY=.ed25519',
|
|
||||||
sequence: 1,
|
|
||||||
timestamp: 1514517067954,
|
|
||||||
hash: 'sha256',
|
|
||||||
content: {
|
|
||||||
type: 'post',
|
|
||||||
text: 'This is the first post!',
|
|
||||||
},
|
|
||||||
signature:
|
|
||||||
'QYOR/zU9dxE1aKBaxc3C0DJ4gRyZtlMfPLt+CGJcY73sv5abKKKxr1SqhOvnm8TY784VHE8kZHCD8RdzFl1tBA==.sig.ed25519',
|
|
||||||
},
|
|
||||||
'js'
|
|
||||||
)
|
|
||||||
|
|
||||||
const msg = await p(ssb.db.add)(nativeMsg)
|
|
||||||
t.equal(msg.value.content.text, 'This is the first post!')
|
|
||||||
|
|
||||||
await p(ssb.close)(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('add() some classic message starting from non-first', async (t) => {
|
|
||||||
const ssb = SecretStack({ appKey: caps.shs })
|
|
||||||
.use(require('../'))
|
|
||||||
.use(require('ssb-classic'))
|
|
||||||
.use(require('ssb-box'))
|
|
||||||
.call(null, {
|
|
||||||
keys: ssbKeys.generate('ed25519', 'alice'),
|
|
||||||
path: DIR,
|
|
||||||
})
|
|
||||||
|
|
||||||
await ssb.db.loaded()
|
|
||||||
|
|
||||||
const nativeMsg1 = classic.toNativeMsg({
|
|
||||||
previous: '%6jh0kDakv0EIu5v9QwDhz9Lz2jEVRTCwyh5sWWzSvSo=.sha256',
|
|
||||||
sequence: 1711,
|
|
||||||
author: '@qeVe7SSpEZxL2Q0sE2jX+TXtMuAgcS889oBZYFDc5WU=.ed25519',
|
|
||||||
timestamp: 1457240385000,
|
|
||||||
hash: 'sha256',
|
|
||||||
content: {
|
|
||||||
type: 'post',
|
|
||||||
text: 'Nulla ullamco laboris proident eu sint cillum. Est proident veniam deserunt quis enim sint reprehenderit voluptate consectetur adipisicing.',
|
|
||||||
root: '%uH8IpYmw6uV1M4uhezcHq1v0xyeJ8J8bQqR/FVm0csM=.sha256',
|
|
||||||
branch: '%SiM9aUnQSk01m0EStBHXD4HLf773OJm998IReSLO1So=.sha256',
|
|
||||||
mentions: [
|
|
||||||
{
|
|
||||||
link: '&bGFib3J1bWRvbG9yYWxpcXVhY29tbW9kb2N1bHBhcGE=.sha256',
|
|
||||||
type: 'image/jpeg',
|
|
||||||
size: 1367352,
|
|
||||||
name: 'commodo cillum',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
link: '@zRr3265aLU/T1/DfB8+Rm+IPDZJnuuRgfurOztIYBi4=.ed25519',
|
|
||||||
name: 'laborum aliquip',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
signature:
|
|
||||||
'ypQ+4ubHo/zcUakMzN4dHqd9qmx06VEADAZPjK0OXbseaEg9s0AWccKgn+WFI0XSO1y7TIphFOA6Dyn6kDzXAg==.sig.ed25519',
|
|
||||||
})
|
|
||||||
|
|
||||||
const nativeMsg2 = classic.toNativeMsg({
|
|
||||||
previous: '%l8drxQMuxpOjUb3RK9rGJl6oPKF4QPHchGvRyqL+IZ4=.sha256',
|
|
||||||
sequence: 1712,
|
|
||||||
author: '@qeVe7SSpEZxL2Q0sE2jX+TXtMuAgcS889oBZYFDc5WU=.ed25519',
|
|
||||||
timestamp: 1457253345000,
|
|
||||||
hash: 'sha256',
|
|
||||||
content: {
|
|
||||||
type: 'post',
|
|
||||||
text: 'Commodo duis eiusmod est tempor eu fugiat commodo sint excepteur non est mollit est exercitation. Sit velit eu quis aute reprehenderit id sit labore quis mollit fugiat magna. Proident eu et proident duis labore irure laboris dolor. Cupidatat aute occaecat proident ut cillum sunt ullamco laborum labore cillum eu ut excepteur laborum aliqua. Magna adipisicing in occaecat adipisicing duis mollit esse. Reprehenderit excepteur labore excepteur qui elit labore velit officia non consectetur id labore ullamco excepteur. Laborum cillum anim ex irure ex proident consequat aute ipsum quis id esse. Exercitation mollit deserunt labore ut eu ea eu consectetur ullamco ex.\nEiusmod qui in proident irure consequat enim duis elit culpa minim dolore nisi aute. Qui anim Lorem consectetur ad do dolore laborum enim aute ex velit eu dolor et incididunt. Nisi nulla aliquip anim irure proident deserunt nostrud in anim elit veniam exercitation aliquip sint. Culpa excepteur sit et eu quis reprehenderit sunt. Id velit reprehenderit nostrud incididunt dolore sint consequat officia pariatur dolore ipsum. Nisi incididunt tempor voluptate fugiat esse. Amet ut elit eu nulla adipisicing non veniam nulla ut culpa.\nDolor adipisicing anim id anim eiusmod laboris aliquip. Anim sint deserunt exercitation nostrud adipisicing amet enim adipisicing Lorem voluptate anim. Sunt pariatur cupidatat culpa dolore ullamco anim. Minim laborum excepteur commodo et aliqua duis reprehenderit exercitation.',
|
|
||||||
root: '%0AwZP5C5aFwzCV5OCxG/2D6Qx70N6ZVIoZ0ZgIu0pPw=.sha256',
|
|
||||||
branch: '%oZF1M4cKj6t2LHloUiegWD1qZ2IIvcLvOPIiVHbQudI=.sha256',
|
|
||||||
},
|
|
||||||
signature:
|
|
||||||
'uWYwWtG2zTmdfpaSTmOghW3QsNCgYNGh5d3VKOFtp2MNQopSCAxjDDER/yfj3k8Bu+NKEnAy5eJ2ylWuxeuEDQ==.sig.ed25519',
|
|
||||||
})
|
|
||||||
|
|
||||||
const msg1 = await p(ssb.db.add)(nativeMsg1)
|
|
||||||
t.equal(msg1.value.sequence, 1711)
|
|
||||||
|
|
||||||
const msg2 = await p(ssb.db.add)(nativeMsg2)
|
|
||||||
t.equal(msg2.value.sequence, 1712)
|
|
||||||
|
|
||||||
await p(ssb.close)(true)
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,57 +1,92 @@
|
||||||
const test = require('tape')
|
const test = require('tape')
|
||||||
const ssbKeys = require('ssb-keys')
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const os = require('os')
|
const os = require('os')
|
||||||
const rimraf = require('rimraf')
|
const rimraf = require('rimraf')
|
||||||
const SecretStack = require('secret-stack')
|
const SecretStack = require('secret-stack')
|
||||||
const caps = require('ssb-caps')
|
const caps = require('ssb-caps')
|
||||||
const p = require('util').promisify
|
const p = require('util').promisify
|
||||||
|
const FeedV1 = require('../lib/feed-v1')
|
||||||
|
const { generateKeypair } = require('./util')
|
||||||
|
|
||||||
const DIR = path.join(os.tmpdir(), 'ppppp-db-create');
|
const DIR = path.join(os.tmpdir(), 'ppppp-db-create')
|
||||||
rimraf.sync(DIR)
|
rimraf.sync(DIR)
|
||||||
|
|
||||||
let ssb
|
const keys = generateKeypair('alice')
|
||||||
|
let peer
|
||||||
test('setup', async (t) => {
|
test('setup', async (t) => {
|
||||||
ssb = SecretStack({ appKey: caps.shs })
|
peer = SecretStack({ appKey: caps.shs })
|
||||||
.use(require('../'))
|
.use(require('../'))
|
||||||
.use(require('ssb-classic'))
|
|
||||||
.use(require('ssb-box'))
|
.use(require('ssb-box'))
|
||||||
.call(null, {
|
.call(null, { keys, path: DIR })
|
||||||
keys: ssbKeys.generate('ed25519', 'alice'),
|
|
||||||
path: DIR,
|
|
||||||
})
|
|
||||||
|
|
||||||
await ssb.db.loaded()
|
await peer.db.loaded()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('create() classic', async (t) => {
|
let msgHash1
|
||||||
const msg1 = await p(ssb.db.create)({
|
let rec1
|
||||||
feedFormat: 'classic',
|
let msgHash2
|
||||||
content: { type: 'post', text: 'I am hungry' },
|
test('create()', async (t) => {
|
||||||
|
rec1 = await p(peer.db.create)({
|
||||||
|
type: 'post',
|
||||||
|
content: { text: 'I am 1st post' },
|
||||||
})
|
})
|
||||||
t.equal(msg1.value.content.text, 'I am hungry', 'msg1 text correct')
|
t.equal(rec1.msg.content.text, 'I am 1st post', 'msg1 text correct')
|
||||||
|
msgHash1 = FeedV1.getMsgHash(rec1.msg)
|
||||||
|
|
||||||
const msg2 = await p(ssb.db.create)({
|
const rec2 = await p(peer.db.create)({
|
||||||
content: { type: 'post', text: 'I am hungry 2' },
|
type: 'post',
|
||||||
feedFormat: 'classic',
|
content: { text: 'I am 2nd post' },
|
||||||
})
|
})
|
||||||
t.equal(msg2.value.content.text, 'I am hungry 2', 'msg2 text correct')
|
t.equal(rec2.msg.content.text, 'I am 2nd post', 'msg2 text correct')
|
||||||
t.equal(msg2.value.previous, msg1.key, 'msg2 previous correct')
|
t.deepEquals(rec2.msg.metadata.prev, [msgHash1], 'msg2 prev correct')
|
||||||
|
msgHash2 = FeedV1.getMsgHash(rec2.msg)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('create() classic box', async (t) => {
|
test('add() forked then create() merged', async (t) => {
|
||||||
const msgBoxed = await p(ssb.db.create)({
|
const msg3 = FeedV1.create({
|
||||||
feedFormat: 'classic',
|
keys,
|
||||||
content: { type: 'post', text: 'I am chewing food', recps: [ssb.id] },
|
when: Date.now(),
|
||||||
|
type: 'post',
|
||||||
|
content: { text: '3rd post forked from 1st' },
|
||||||
|
prev: [rec1.msg],
|
||||||
|
})
|
||||||
|
|
||||||
|
const rec3 = await p(peer.db.add)(msg3)
|
||||||
|
const msgHash3 = FeedV1.getMsgHash(rec3.msg)
|
||||||
|
|
||||||
|
const rec4 = await p(peer.db.create)({
|
||||||
|
type: 'post',
|
||||||
|
content: { text: 'I am 4th post' },
|
||||||
|
})
|
||||||
|
t.ok(rec4, '4th post created')
|
||||||
|
t.deepEquals(
|
||||||
|
rec4.msg.metadata.prev,
|
||||||
|
[msgHash2, msgHash3],
|
||||||
|
'msg4 prev is msg2 and msg3'
|
||||||
|
)
|
||||||
|
const msgHash4 = FeedV1.getMsgHash(rec4.msg)
|
||||||
|
|
||||||
|
const rec5 = await p(peer.db.create)({
|
||||||
|
type: 'post',
|
||||||
|
content: { text: 'I am 5th post' },
|
||||||
|
})
|
||||||
|
t.ok(rec5, '5th post created')
|
||||||
|
t.deepEquals(rec5.msg.metadata.prev, [msgHash4], 'msg5 prev is msg4')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('create() encrypted with box', async (t) => {
|
||||||
|
const recEncrypted = await p(peer.db.create)({
|
||||||
|
type: 'post',
|
||||||
|
content: { text: 'I am chewing food', recps: [peer.id] },
|
||||||
encryptionFormat: 'box',
|
encryptionFormat: 'box',
|
||||||
})
|
})
|
||||||
t.equal(typeof msgBoxed.value.content, 'string')
|
t.equal(typeof recEncrypted.msg.content, 'string')
|
||||||
t.true(msgBoxed.value.content.endsWith('.box'), '.box')
|
t.true(recEncrypted.msg.content.endsWith('.box'), '.box')
|
||||||
|
|
||||||
const msgVal = ssb.db.get(msgBoxed.key)
|
const msgDecrypted = peer.db.get(recEncrypted.id)
|
||||||
t.equals(msgVal.content.text, 'I am chewing food')
|
t.equals(msgDecrypted.content.text, 'I am chewing food')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('teardown', (t) => {
|
test('teardown', (t) => {
|
||||||
ssb.close(t.end)
|
peer.close(t.end)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
const test = require('tape')
|
const test = require('tape')
|
||||||
const ssbKeys = require('ssb-keys')
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const os = require('os')
|
const os = require('os')
|
||||||
const rimraf = require('rimraf')
|
const rimraf = require('rimraf')
|
||||||
|
@ -8,47 +7,47 @@ const AAOL = require('async-append-only-log')
|
||||||
const push = require('push-stream')
|
const push = require('push-stream')
|
||||||
const caps = require('ssb-caps')
|
const caps = require('ssb-caps')
|
||||||
const p = require('util').promisify
|
const p = require('util').promisify
|
||||||
|
const { generateKeypair } = require('./util')
|
||||||
|
|
||||||
const DIR = path.join(os.tmpdir(), 'ppppp-db-del')
|
const DIR = path.join(os.tmpdir(), 'ppppp-db-del')
|
||||||
rimraf.sync(DIR)
|
rimraf.sync(DIR)
|
||||||
|
|
||||||
test('del', async (t) => {
|
test('del', async (t) => {
|
||||||
const ssb = SecretStack({ appKey: caps.shs })
|
const keys = generateKeypair('alice')
|
||||||
|
const peer = SecretStack({ appKey: caps.shs })
|
||||||
.use(require('../'))
|
.use(require('../'))
|
||||||
.use(require('ssb-classic'))
|
.call(null, { keys, path: DIR })
|
||||||
.call(null, {
|
|
||||||
keys: ssbKeys.generate('ed25519', 'alice'),
|
|
||||||
path: DIR,
|
|
||||||
})
|
|
||||||
|
|
||||||
await ssb.db.loaded()
|
await peer.db.loaded()
|
||||||
|
|
||||||
const msgIDs = []
|
const msgIDs = []
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
const msg = await p(ssb.db.create)({
|
const rec = await p(peer.db.create)({
|
||||||
feedFormat: 'classic',
|
type: 'post',
|
||||||
content: { type: 'post', text: 'm' + i },
|
content: { text: 'm' + i },
|
||||||
})
|
})
|
||||||
msgIDs.push(msg.key)
|
msgIDs.push(rec.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const before = ssb.db
|
const before = []
|
||||||
.filterAsArray(() => true)
|
for (const msg of peer.db.msgs()) {
|
||||||
.map((msg) => msg.value.content.text)
|
before.push(msg.content.text)
|
||||||
|
}
|
||||||
|
|
||||||
t.deepEqual(before, ['m0', 'm1', 'm2', 'm3', 'm4'], 'msgs before the delete')
|
t.deepEqual(before, ['m0', 'm1', 'm2', 'm3', 'm4'], 'msgs before the delete')
|
||||||
|
|
||||||
await p(ssb.db.del)(msgIDs[2])
|
await p(peer.db.del)(msgIDs[2])
|
||||||
|
|
||||||
const after = ssb.db
|
const after = []
|
||||||
.filterAsArray(() => true)
|
for (const msg of peer.db.msgs()) {
|
||||||
.map((msg) => msg?.value.content.text ?? null)
|
after.push(msg.content.text)
|
||||||
|
}
|
||||||
|
|
||||||
t.deepEqual(after, ['m0', 'm1', null, 'm3', 'm4'], 'msgs after the delete')
|
t.deepEqual(after, ['m0', 'm1', 'm3', 'm4'], 'msgs after the delete')
|
||||||
|
|
||||||
await p(ssb.close)(true)
|
await p(peer.close)(true)
|
||||||
|
|
||||||
const log = AAOL(path.join(DIR, 'memdb-log.bin'), {
|
const log = AAOL(path.join(DIR, 'db.bin'), {
|
||||||
cacheSize: 1,
|
cacheSize: 1,
|
||||||
blockSize: 64 * 1024,
|
blockSize: 64 * 1024,
|
||||||
codec: {
|
codec: {
|
||||||
|
@ -66,10 +65,8 @@ test('del', async (t) => {
|
||||||
log.stream({ offsets: true, values: true, sizes: true }).pipe(
|
log.stream({ offsets: true, values: true, sizes: true }).pipe(
|
||||||
push.drain(
|
push.drain(
|
||||||
function drainEach({ offset, value, size }) {
|
function drainEach({ offset, value, size }) {
|
||||||
if (!value) {
|
if (value) {
|
||||||
persistedMsgs.push(null)
|
persistedMsgs.push(value.msg)
|
||||||
} else {
|
|
||||||
persistedMsgs.push(value)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function drainEnd(err) {
|
function drainEnd(err) {
|
||||||
|
@ -81,8 +78,8 @@ test('del', async (t) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
persistedMsgs.map((msg) => msg?.value.content.text ?? null),
|
persistedMsgs.map((msg) => msg.content.text),
|
||||||
['m0', 'm1', null, 'm3', 'm4'],
|
['m0', 'm1', 'm3', 'm4'],
|
||||||
'msgs in disk after the delete'
|
'msgs in disk after the delete'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,91 +1,82 @@
|
||||||
const tape = require('tape')
|
const tape = require('tape')
|
||||||
const dagfeed = require('../lib/feed-v1')
|
const FeedV1 = require('../lib/feed-v1')
|
||||||
const { generateKeypair } = require('./util')
|
const { generateKeypair } = require('./util')
|
||||||
|
|
||||||
tape('encode/decode works', (t) => {
|
tape('encode/decode works', (t) => {
|
||||||
const keys = generateKeypair('alice')
|
const keys = generateKeypair('alice')
|
||||||
const hmacKey = null
|
|
||||||
const content = { text: 'Hello world!' }
|
const content = { text: 'Hello world!' }
|
||||||
const timestamp = 1652037377204
|
const when = 1652037377204
|
||||||
|
|
||||||
const nmsg1 = dagfeed.newNativeMsg({
|
const msg1 = FeedV1.create({
|
||||||
keys,
|
keys,
|
||||||
content,
|
content,
|
||||||
type: 'post',
|
type: 'post',
|
||||||
previous: [],
|
prev: [],
|
||||||
timestamp,
|
when,
|
||||||
hmacKey,
|
|
||||||
})
|
})
|
||||||
|
t.deepEquals(
|
||||||
|
Object.keys(msg1.metadata),
|
||||||
|
['depth', 'prev', 'proof', 'size', 'type', 'who', 'when'],
|
||||||
|
'metadata fields'
|
||||||
|
)
|
||||||
t.equals(
|
t.equals(
|
||||||
nmsg1.metadata.author,
|
msg1.metadata.who,
|
||||||
'4mjQ5aJu378cEu6TksRG3uXAiKFiwGjYQtWAjfVjDAJW',
|
'4mjQ5aJu378cEu6TksRG3uXAiKFiwGjYQtWAjfVjDAJW',
|
||||||
'metadata.author is correct'
|
'metadata.who'
|
||||||
)
|
)
|
||||||
t.equals(nmsg1.metadata.type, 'post', 'metadata.type is correct')
|
t.equals(msg1.metadata.type, 'post', 'metadata.type')
|
||||||
t.deepEquals(nmsg1.metadata.previous, [], 'metadata.previous is correct')
|
t.equals(msg1.metadata.depth, 0, 'metadata.depth')
|
||||||
console.log(nmsg1)
|
t.deepEquals(msg1.metadata.prev, [], 'metadata.prev')
|
||||||
|
t.deepEquals(msg1.metadata.proof, '9R7XmBhHF5ooPg34j9TQcz', 'metadata.proof')
|
||||||
|
t.deepEquals(msg1.metadata.size, 23, 'metadata.size')
|
||||||
|
t.equals(typeof msg1.metadata.when, 'number', 'metadata.when')
|
||||||
|
t.deepEqual(msg1.content, content, 'content is correct')
|
||||||
|
|
||||||
const jsonMsg = {
|
console.log(msg1)
|
||||||
key: dagfeed.getMsgId(nmsg1),
|
|
||||||
value: dagfeed.fromNativeMsg(nmsg1),
|
|
||||||
timestamp: Date.now(),
|
|
||||||
}
|
|
||||||
|
|
||||||
const msgHash1 = 'HEzse89DSDWUXVPyav35GC'
|
const msgHash = '9cYegpVpddoMSdvSf53dTH'
|
||||||
const msgKey1 =
|
|
||||||
'ssb:message/dag/4mjQ5aJu378cEu6TksRG3uXAiKFiwGjYQtWAjfVjDAJW/post/' +
|
|
||||||
msgHash1
|
|
||||||
|
|
||||||
t.deepEqual(jsonMsg.key, msgKey1, 'key is correct')
|
t.equals(
|
||||||
t.deepEqual(
|
FeedV1.getMsgId(msg1),
|
||||||
jsonMsg.value.author,
|
'ppppp:message/v1/4mjQ5aJu378cEu6TksRG3uXAiKFiwGjYQtWAjfVjDAJW/post/' +
|
||||||
'ssb:feed/dag/4mjQ5aJu378cEu6TksRG3uXAiKFiwGjYQtWAjfVjDAJW/post',
|
msgHash,
|
||||||
'author is correct'
|
'getMsgId'
|
||||||
)
|
)
|
||||||
t.deepEqual(jsonMsg.value.type, 'post', 'correct type')
|
|
||||||
t.equals(typeof jsonMsg.value.timestamp, 'number', 'has timestamp')
|
|
||||||
t.deepEqual(jsonMsg.value.previous, [], 'correct previous')
|
|
||||||
t.deepEqual(jsonMsg.value.content, content, 'content is the same')
|
|
||||||
|
|
||||||
const reconstructedNMsg1 = dagfeed.toNativeMsg(jsonMsg.value)
|
const content2 = { text: 'Ola mundo!' }
|
||||||
t.deepEqual(reconstructedNMsg1, nmsg1, 'can reconstruct')
|
|
||||||
|
|
||||||
const content2 = { text: 'Hello butty world!' }
|
const msg2 = FeedV1.create({
|
||||||
|
|
||||||
const nmsg2 = dagfeed.newNativeMsg({
|
|
||||||
keys,
|
keys,
|
||||||
content: content2,
|
content: content2,
|
||||||
type: 'post',
|
type: 'post',
|
||||||
previous: [msgHash1],
|
prev: [msg1],
|
||||||
timestamp: timestamp + 1,
|
when: when + 1,
|
||||||
hmacKey,
|
|
||||||
})
|
})
|
||||||
console.log(nmsg2)
|
t.deepEquals(
|
||||||
|
Object.keys(msg2.metadata),
|
||||||
|
['depth', 'prev', 'proof', 'size', 'type', 'who', 'when'],
|
||||||
|
'metadata keys'
|
||||||
|
)
|
||||||
|
t.equals(
|
||||||
|
msg2.metadata.who,
|
||||||
|
'4mjQ5aJu378cEu6TksRG3uXAiKFiwGjYQtWAjfVjDAJW',
|
||||||
|
'metadata.who'
|
||||||
|
)
|
||||||
|
t.equals(msg2.metadata.type, 'post', 'metadata.type')
|
||||||
|
t.equals(msg2.metadata.depth, 1, 'metadata.depth')
|
||||||
|
t.deepEquals(msg2.metadata.prev, [msgHash], 'metadata.prev')
|
||||||
|
t.deepEquals(msg2.metadata.proof, 'XuZEzH1Dhy1yuRMcviBBcN', 'metadata.proof')
|
||||||
|
t.deepEquals(msg2.metadata.size, 21, 'metadata.size')
|
||||||
|
t.equals(typeof msg2.metadata.when, 'number', 'metadata.when')
|
||||||
|
t.deepEqual(msg2.content, content2, 'content is correct')
|
||||||
|
|
||||||
const jsonMsg2 = {
|
console.log(msg2)
|
||||||
key: dagfeed.getMsgId(nmsg2),
|
|
||||||
value: dagfeed.fromNativeMsg(nmsg2),
|
|
||||||
timestamp: Date.now(),
|
|
||||||
}
|
|
||||||
|
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
jsonMsg2.key,
|
FeedV1.getMsgId(msg2),
|
||||||
'ssb:message/dag/4mjQ5aJu378cEu6TksRG3uXAiKFiwGjYQtWAjfVjDAJW/post/U5n4v1m7gFzrtrdK84gGsV',
|
'ppppp:message/v1/4mjQ5aJu378cEu6TksRG3uXAiKFiwGjYQtWAjfVjDAJW/post/LEH1JVENvJgSpBBrVUwJx6',
|
||||||
'key is correct'
|
'getMsgId'
|
||||||
)
|
)
|
||||||
t.deepEqual(
|
|
||||||
jsonMsg2.value.author,
|
|
||||||
'ssb:feed/dag/4mjQ5aJu378cEu6TksRG3uXAiKFiwGjYQtWAjfVjDAJW/post',
|
|
||||||
'author is correct'
|
|
||||||
)
|
|
||||||
t.deepEqual(jsonMsg2.value.type, 'post', 'correct type')
|
|
||||||
t.equals(typeof jsonMsg2.value.timestamp, 'number', 'has timestamp')
|
|
||||||
t.deepEqual(jsonMsg2.value.previous, [msgKey1], 'correct previous')
|
|
||||||
t.deepEqual(jsonMsg2.value.content, content2, 'content is the same')
|
|
||||||
|
|
||||||
// test slow version as well
|
|
||||||
const reconstructedNMsg2 = dagfeed.toNativeMsg(jsonMsg2.value)
|
|
||||||
t.deepEqual(reconstructedNMsg2, nmsg2, 'can reconstruct')
|
|
||||||
|
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,224 @@
|
||||||
|
const tape = require('tape')
|
||||||
|
const base58 = require('bs58')
|
||||||
|
const FeedV1 = require('../lib/feed-v1')
|
||||||
|
const { generateKeypair } = require('./util')
|
||||||
|
|
||||||
|
tape('invalid 1st msg with non-empty prev', (t) => {
|
||||||
|
const keys = generateKeypair('alice')
|
||||||
|
|
||||||
|
const msg = FeedV1.create({
|
||||||
|
keys,
|
||||||
|
content: { text: 'Hello world!' },
|
||||||
|
type: 'post',
|
||||||
|
prev: [{ metadata: { depth: 10 }, sig: 'fake' }],
|
||||||
|
when: 1652030001000,
|
||||||
|
})
|
||||||
|
|
||||||
|
FeedV1.validate(msg, new Map(), (err) => {
|
||||||
|
t.ok(err, 'invalid 2nd msg throws')
|
||||||
|
t.match(
|
||||||
|
err.message,
|
||||||
|
/prev .+ is not locally known/,
|
||||||
|
'invalid 2nd msg description'
|
||||||
|
)
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
tape('invalid 1st msg with non-array prev', (t) => {
|
||||||
|
const keys = generateKeypair('alice')
|
||||||
|
|
||||||
|
const msg = FeedV1.create({
|
||||||
|
keys,
|
||||||
|
content: { text: 'Hello world!' },
|
||||||
|
type: 'post',
|
||||||
|
prev: [],
|
||||||
|
when: 1652030001000,
|
||||||
|
})
|
||||||
|
msg.metadata.prev = null
|
||||||
|
|
||||||
|
FeedV1.validate(msg, new Map(), (err) => {
|
||||||
|
t.ok(err, 'invalid 2nd msg throws')
|
||||||
|
t.match(err.message, /prev must be an array/, 'invalid 2nd msg description')
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
tape('invalid msg with non-array prev', (t) => {
|
||||||
|
const keys = generateKeypair('alice')
|
||||||
|
|
||||||
|
const msg1 = FeedV1.create({
|
||||||
|
keys,
|
||||||
|
content: { text: 'Hello world!' },
|
||||||
|
type: 'post',
|
||||||
|
prev: [],
|
||||||
|
when: 1652030001000,
|
||||||
|
})
|
||||||
|
const msgHash1 = FeedV1.getMsgHash(msg1)
|
||||||
|
|
||||||
|
const msg2 = FeedV1.create({
|
||||||
|
keys,
|
||||||
|
content: { text: 'Hello world!' },
|
||||||
|
type: 'post',
|
||||||
|
prev: [{ metadata: { depth: 10 }, sig: 'fake' }],
|
||||||
|
when: 1652030002000,
|
||||||
|
})
|
||||||
|
msg2.metadata.prev = null
|
||||||
|
|
||||||
|
const existing = new Map()
|
||||||
|
existing.set(msgHash1, msg1)
|
||||||
|
FeedV1.validate(msg2, existing, (err) => {
|
||||||
|
t.ok(err, 'invalid 2nd msg throws')
|
||||||
|
t.match(
|
||||||
|
err.message,
|
||||||
|
/prev must be an iterator/,
|
||||||
|
'invalid 2nd msg description'
|
||||||
|
)
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
tape('invalid msg with bad prev', (t) => {
|
||||||
|
const keys = generateKeypair('alice')
|
||||||
|
|
||||||
|
const msg1 = FeedV1.create({
|
||||||
|
keys,
|
||||||
|
content: { text: 'Hello world!' },
|
||||||
|
type: 'post',
|
||||||
|
prev: [],
|
||||||
|
when: 1652030001000,
|
||||||
|
})
|
||||||
|
const msgHash1 = FeedV1.getMsgHash(msg1)
|
||||||
|
|
||||||
|
const msg2 = FeedV1.create({
|
||||||
|
keys,
|
||||||
|
content: { text: 'Hello world!' },
|
||||||
|
type: 'post',
|
||||||
|
prev: [{ metadata: { depth: 10 }, sig: 'fake' }],
|
||||||
|
when: 1652030002000,
|
||||||
|
})
|
||||||
|
msg2.metadata.prev = [1234]
|
||||||
|
|
||||||
|
const existing = new Map()
|
||||||
|
existing.set(msgHash1, msg1)
|
||||||
|
FeedV1.validate(msg2, existing, (err) => {
|
||||||
|
t.ok(err, 'invalid 2nd msg throws')
|
||||||
|
t.match(
|
||||||
|
err.message,
|
||||||
|
/prev must contain strings/,
|
||||||
|
'invalid 2nd msg description'
|
||||||
|
)
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
tape('invalid msg with URI in prev', (t) => {
|
||||||
|
const keys = generateKeypair('alice')
|
||||||
|
|
||||||
|
const msg1 = FeedV1.create({
|
||||||
|
keys,
|
||||||
|
content: { text: 'Hello world!' },
|
||||||
|
type: 'post',
|
||||||
|
prev: [],
|
||||||
|
when: 1652030001000,
|
||||||
|
})
|
||||||
|
const msgHash1 = FeedV1.getMsgHash(msg1)
|
||||||
|
|
||||||
|
const msg2 = FeedV1.create({
|
||||||
|
keys,
|
||||||
|
content: { text: 'Hello world!' },
|
||||||
|
type: 'post',
|
||||||
|
prev: [{ metadata: { depth: 10 }, sig: 'fake' }],
|
||||||
|
when: 1652030002000,
|
||||||
|
})
|
||||||
|
const randBuf = Buffer.alloc(16).fill(16)
|
||||||
|
const fakeMsgKey1 = `ppppp:message/v1/${base58.encode(randBuf)}`
|
||||||
|
msg2.metadata.prev = [fakeMsgKey1]
|
||||||
|
|
||||||
|
const existing = new Map()
|
||||||
|
existing.set(msgHash1, msg1)
|
||||||
|
FeedV1.validate(msg2, existing, (err) => {
|
||||||
|
t.ok(err, 'invalid 2nd msg throws')
|
||||||
|
t.match(
|
||||||
|
err.message,
|
||||||
|
/prev must not contain URIs/,
|
||||||
|
'invalid 2nd msg description'
|
||||||
|
)
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
tape('invalid msg with unknown prev', (t) => {
|
||||||
|
const keys = generateKeypair('alice')
|
||||||
|
|
||||||
|
const msg1 = FeedV1.create({
|
||||||
|
keys,
|
||||||
|
content: { text: 'Hello world!' },
|
||||||
|
type: 'post',
|
||||||
|
prev: [],
|
||||||
|
when: 1652030001000,
|
||||||
|
})
|
||||||
|
const msgHash1 = FeedV1.getMsgHash(msg1)
|
||||||
|
|
||||||
|
const unknownMsg = FeedV1.create({
|
||||||
|
keys,
|
||||||
|
content: { text: 'Alien' },
|
||||||
|
type: 'post',
|
||||||
|
prev: [],
|
||||||
|
when: 1652030001000,
|
||||||
|
})
|
||||||
|
|
||||||
|
const msg2 = FeedV1.create({
|
||||||
|
keys,
|
||||||
|
content: { text: 'Hello world!' },
|
||||||
|
type: 'post',
|
||||||
|
prev: [unknownMsg],
|
||||||
|
when: 1652030002000,
|
||||||
|
})
|
||||||
|
|
||||||
|
const existing = new Map()
|
||||||
|
existing.set(msgHash1, msg1)
|
||||||
|
FeedV1.validate(msg2, existing, (err) => {
|
||||||
|
t.ok(err, 'invalid 2nd msg throws')
|
||||||
|
t.match(
|
||||||
|
err.message,
|
||||||
|
/prev .+ is not locally known/,
|
||||||
|
'invalid 2nd msg description'
|
||||||
|
)
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
tape('invalid msg with unknown prev in a Set', (t) => {
|
||||||
|
const keys = generateKeypair('alice')
|
||||||
|
|
||||||
|
const msg1 = FeedV1.create({
|
||||||
|
keys,
|
||||||
|
content: { text: 'Hello world!' },
|
||||||
|
type: 'post',
|
||||||
|
prev: [],
|
||||||
|
when: 1652030001000,
|
||||||
|
})
|
||||||
|
|
||||||
|
const msg2 = FeedV1.create({
|
||||||
|
keys,
|
||||||
|
content: { text: 'Hello world!' },
|
||||||
|
type: 'post',
|
||||||
|
prev: [{ metadata: { depth: 10 }, sig: 'fake' }],
|
||||||
|
when: 1652030002000,
|
||||||
|
})
|
||||||
|
const fakeMsgKey1 = base58.encode(Buffer.alloc(16).fill(42))
|
||||||
|
msg2.metadata.prev = [fakeMsgKey1]
|
||||||
|
|
||||||
|
const existing = new Set([msg1])
|
||||||
|
|
||||||
|
FeedV1.validate(msg2, existing, (err) => {
|
||||||
|
t.ok(err, 'invalid 2nd msg throws')
|
||||||
|
t.match(
|
||||||
|
err.message,
|
||||||
|
/prev .+ is not locally known/,
|
||||||
|
'invalid 2nd msg description'
|
||||||
|
)
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,241 +0,0 @@
|
||||||
const tape = require('tape')
|
|
||||||
const base58 = require('bs58')
|
|
||||||
const dagfeed = require('../lib/feed-v1')
|
|
||||||
const { generateKeypair } = require('./util')
|
|
||||||
|
|
||||||
tape('invalid 1st msg with non-empty previous', (t) => {
|
|
||||||
const keys = generateKeypair('alice')
|
|
||||||
const hmacKey = null
|
|
||||||
|
|
||||||
const fakeMsgKey0 = base58.encode(Buffer.alloc(16).fill(42))
|
|
||||||
|
|
||||||
const nmsg1 = dagfeed.newNativeMsg({
|
|
||||||
keys,
|
|
||||||
content: { text: 'Hello world!' },
|
|
||||||
type: 'post',
|
|
||||||
previous: [fakeMsgKey0],
|
|
||||||
timestamp: 1652030001000,
|
|
||||||
hmacKey,
|
|
||||||
})
|
|
||||||
|
|
||||||
dagfeed.validate(nmsg1, [], null, (err) => {
|
|
||||||
t.ok(err, 'invalid 2nd msg throws')
|
|
||||||
t.match(
|
|
||||||
err.message,
|
|
||||||
/previous must be an empty array/,
|
|
||||||
'invalid 2nd msg description'
|
|
||||||
)
|
|
||||||
t.end()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
tape('invalid 1st msg with non-array previous', (t) => {
|
|
||||||
const keys = generateKeypair('alice')
|
|
||||||
const hmacKey = null
|
|
||||||
|
|
||||||
const nmsg1 = dagfeed.newNativeMsg({
|
|
||||||
keys,
|
|
||||||
content: { text: 'Hello world!' },
|
|
||||||
type: 'post',
|
|
||||||
previous: [],
|
|
||||||
timestamp: 1652030001000,
|
|
||||||
hmacKey,
|
|
||||||
})
|
|
||||||
nmsg1.metadata.previous = null
|
|
||||||
|
|
||||||
dagfeed.validate(nmsg1, [], null, (err) => {
|
|
||||||
t.ok(err, 'invalid 2nd msg throws')
|
|
||||||
t.match(
|
|
||||||
err.message,
|
|
||||||
/previous must be an array/,
|
|
||||||
'invalid 2nd msg description'
|
|
||||||
)
|
|
||||||
t.end()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
tape('invalid msg with non-array previous', (t) => {
|
|
||||||
const keys = generateKeypair('alice')
|
|
||||||
const hmacKey = null
|
|
||||||
|
|
||||||
const nmsg1 = dagfeed.newNativeMsg({
|
|
||||||
keys,
|
|
||||||
content: { text: 'Hello world!' },
|
|
||||||
type: 'post',
|
|
||||||
previous: [],
|
|
||||||
timestamp: 1652030001000,
|
|
||||||
hmacKey,
|
|
||||||
})
|
|
||||||
|
|
||||||
const fakeMsgKey1 = `ssb:message/dag/${base58.encode(
|
|
||||||
Buffer.alloc(16).fill(42)
|
|
||||||
)}`
|
|
||||||
|
|
||||||
const nmsg2 = dagfeed.newNativeMsg({
|
|
||||||
keys,
|
|
||||||
content: { text: 'Hello world!' },
|
|
||||||
type: 'post',
|
|
||||||
previous: [fakeMsgKey1],
|
|
||||||
timestamp: 1652030002000,
|
|
||||||
hmacKey,
|
|
||||||
})
|
|
||||||
nmsg2.metadata.previous = null
|
|
||||||
|
|
||||||
dagfeed.validate(nmsg2, [nmsg1], null, (err) => {
|
|
||||||
t.ok(err, 'invalid 2nd msg throws')
|
|
||||||
t.match(
|
|
||||||
err.message,
|
|
||||||
/previous must be an array/,
|
|
||||||
'invalid 2nd msg description'
|
|
||||||
)
|
|
||||||
t.end()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
tape('invalid msg with bad previous', (t) => {
|
|
||||||
const keys = generateKeypair('alice')
|
|
||||||
const hmacKey = null
|
|
||||||
|
|
||||||
const nmsg1 = dagfeed.newNativeMsg({
|
|
||||||
keys,
|
|
||||||
content: { text: 'Hello world!' },
|
|
||||||
type: 'post',
|
|
||||||
previous: [],
|
|
||||||
timestamp: 1652030001000,
|
|
||||||
hmacKey,
|
|
||||||
})
|
|
||||||
|
|
||||||
const fakeMsgKey1 = `ssb:message/dag/${base58.encode(
|
|
||||||
Buffer.alloc(16).fill(42)
|
|
||||||
)}`
|
|
||||||
|
|
||||||
const nmsg2 = dagfeed.newNativeMsg({
|
|
||||||
keys,
|
|
||||||
content: { text: 'Hello world!' },
|
|
||||||
type: 'post',
|
|
||||||
previous: [fakeMsgKey1],
|
|
||||||
timestamp: 1652030002000,
|
|
||||||
hmacKey,
|
|
||||||
})
|
|
||||||
nmsg2.metadata.previous = [1234]
|
|
||||||
|
|
||||||
dagfeed.validate(nmsg2, [nmsg1], null, (err) => {
|
|
||||||
t.ok(err, 'invalid 2nd msg throws')
|
|
||||||
t.match(
|
|
||||||
err.message,
|
|
||||||
/previous must contain strings/,
|
|
||||||
'invalid 2nd msg description'
|
|
||||||
)
|
|
||||||
t.end()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
tape('invalid msg with SSB URI previous', (t) => {
|
|
||||||
const keys = generateKeypair('alice')
|
|
||||||
const hmacKey = null
|
|
||||||
|
|
||||||
const nmsg1 = dagfeed.newNativeMsg({
|
|
||||||
keys,
|
|
||||||
content: { text: 'Hello world!' },
|
|
||||||
type: 'post',
|
|
||||||
previous: [],
|
|
||||||
timestamp: 1652030001000,
|
|
||||||
hmacKey,
|
|
||||||
})
|
|
||||||
|
|
||||||
const fakeMsgKey1 = `ssb:message/dag/${base58.encode(
|
|
||||||
Buffer.alloc(16).fill(42)
|
|
||||||
)}`
|
|
||||||
|
|
||||||
const nmsg2 = dagfeed.newNativeMsg({
|
|
||||||
keys,
|
|
||||||
content: { text: 'Hello world!' },
|
|
||||||
type: 'post',
|
|
||||||
previous: [fakeMsgKey1],
|
|
||||||
timestamp: 1652030002000,
|
|
||||||
hmacKey,
|
|
||||||
})
|
|
||||||
nmsg2.metadata.previous = [fakeMsgKey1]
|
|
||||||
|
|
||||||
dagfeed.validate(nmsg2, [nmsg1], null, (err) => {
|
|
||||||
t.ok(err, 'invalid 2nd msg throws')
|
|
||||||
t.match(
|
|
||||||
err.message,
|
|
||||||
/previous must not contain SSB URIs/,
|
|
||||||
'invalid 2nd msg description'
|
|
||||||
)
|
|
||||||
t.end()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
tape('invalid msg with unknown previous', (t) => {
|
|
||||||
const keys = generateKeypair('alice')
|
|
||||||
const hmacKey = null
|
|
||||||
|
|
||||||
const nmsg1 = dagfeed.newNativeMsg({
|
|
||||||
keys,
|
|
||||||
content: { text: 'Hello world!' },
|
|
||||||
type: 'post',
|
|
||||||
previous: [],
|
|
||||||
timestamp: 1652030001000,
|
|
||||||
hmacKey,
|
|
||||||
})
|
|
||||||
|
|
||||||
const fakeMsgKey1 = base58.encode(Buffer.alloc(16).fill(42))
|
|
||||||
|
|
||||||
const nmsg2 = dagfeed.newNativeMsg({
|
|
||||||
keys,
|
|
||||||
content: { text: 'Hello world!' },
|
|
||||||
type: 'post',
|
|
||||||
previous: [fakeMsgKey1],
|
|
||||||
timestamp: 1652030002000,
|
|
||||||
hmacKey,
|
|
||||||
})
|
|
||||||
|
|
||||||
dagfeed.validate(nmsg2, [nmsg1], null, (err) => {
|
|
||||||
t.ok(err, 'invalid 2nd msg throws')
|
|
||||||
t.match(
|
|
||||||
err.message,
|
|
||||||
/previous .+ is not a known message ID/,
|
|
||||||
'invalid 2nd msg description'
|
|
||||||
)
|
|
||||||
t.end()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
tape('invalid msg with unknown previous in a Set', (t) => {
|
|
||||||
const keys = generateKeypair('alice')
|
|
||||||
const hmacKey = null
|
|
||||||
|
|
||||||
const nmsg1 = dagfeed.newNativeMsg({
|
|
||||||
keys,
|
|
||||||
content: { text: 'Hello world!' },
|
|
||||||
type: 'post',
|
|
||||||
previous: [],
|
|
||||||
timestamp: 1652030001000,
|
|
||||||
hmacKey,
|
|
||||||
})
|
|
||||||
|
|
||||||
const fakeMsgKey1 = base58.encode(Buffer.alloc(16).fill(42))
|
|
||||||
|
|
||||||
const nmsg2 = dagfeed.newNativeMsg({
|
|
||||||
keys,
|
|
||||||
content: { text: 'Hello world!' },
|
|
||||||
type: 'post',
|
|
||||||
previous: [fakeMsgKey1],
|
|
||||||
timestamp: 1652030002000,
|
|
||||||
hmacKey,
|
|
||||||
})
|
|
||||||
|
|
||||||
const existing = new Set([nmsg1])
|
|
||||||
|
|
||||||
dagfeed.validate(nmsg2, existing, null, (err) => {
|
|
||||||
t.ok(err, 'invalid 2nd msg throws')
|
|
||||||
t.match(
|
|
||||||
err.message,
|
|
||||||
/previous .+ is not a known message ID/,
|
|
||||||
'invalid 2nd msg description'
|
|
||||||
)
|
|
||||||
t.end()
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,20 +1,18 @@
|
||||||
const tape = require('tape')
|
const tape = require('tape')
|
||||||
const dagfeed = require('../lib/feed-v1')
|
const FeedV1 = require('../lib/feed-v1')
|
||||||
const { generateKeypair } = require('./util')
|
const { generateKeypair } = require('./util')
|
||||||
|
|
||||||
tape('invalid type not a string', function (t) {
|
tape('invalid type not a string', (t) => {
|
||||||
const keys = generateKeypair('alice')
|
const keys = generateKeypair('alice')
|
||||||
const hmacKey = null
|
|
||||||
|
|
||||||
t.throws(
|
t.throws(
|
||||||
() => {
|
() => {
|
||||||
dagfeed.newNativeMsg({
|
FeedV1.create({
|
||||||
keys,
|
keys,
|
||||||
content: { text: 'Hello world!' },
|
content: { text: 'Hello world!' },
|
||||||
timestamp: 1652037377204,
|
when: 1652037377204,
|
||||||
type: 123,
|
type: 123,
|
||||||
previous: [],
|
prev: [],
|
||||||
hmacKey,
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/type is not a string/,
|
/type is not a string/,
|
||||||
|
@ -23,19 +21,17 @@ tape('invalid type not a string', function (t) {
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
tape('invalid type with "/" character', function (t) {
|
tape('invalid type with "/" character', (t) => {
|
||||||
const keys = generateKeypair('alice')
|
const keys = generateKeypair('alice')
|
||||||
const hmacKey = null
|
|
||||||
|
|
||||||
t.throws(
|
t.throws(
|
||||||
() => {
|
() => {
|
||||||
dagfeed.newNativeMsg({
|
FeedV1.create({
|
||||||
keys,
|
keys,
|
||||||
content: { text: 'Hello world!' },
|
content: { text: 'Hello world!' },
|
||||||
timestamp: 1652037377204,
|
when: 1652037377204,
|
||||||
type: 'group/init',
|
type: 'group/init',
|
||||||
previous: [],
|
prev: [],
|
||||||
hmacKey,
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/invalid type/,
|
/invalid type/,
|
||||||
|
@ -44,19 +40,17 @@ tape('invalid type with "/" character', function (t) {
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
tape('invalid type with "*" character', function (t) {
|
tape('invalid type with "*" character', (t) => {
|
||||||
const keys = generateKeypair('alice')
|
const keys = generateKeypair('alice')
|
||||||
const hmacKey = null
|
|
||||||
|
|
||||||
t.throws(
|
t.throws(
|
||||||
() => {
|
() => {
|
||||||
dagfeed.newNativeMsg({
|
FeedV1.create({
|
||||||
keys,
|
keys,
|
||||||
content: { text: 'Hello world!' },
|
content: { text: 'Hello world!' },
|
||||||
timestamp: 1652037377204,
|
when: 1652037377204,
|
||||||
type: 'star*',
|
type: 'star*',
|
||||||
previous: [],
|
prev: [],
|
||||||
hmacKey,
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/invalid type/,
|
/invalid type/,
|
||||||
|
@ -65,19 +59,17 @@ tape('invalid type with "*" character', function (t) {
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
tape('invalid type too short', function (t) {
|
tape('invalid type too short', (t) => {
|
||||||
const keys = generateKeypair('alice')
|
const keys = generateKeypair('alice')
|
||||||
const hmacKey = null
|
|
||||||
|
|
||||||
t.throws(
|
t.throws(
|
||||||
() => {
|
() => {
|
||||||
dagfeed.newNativeMsg({
|
FeedV1.create({
|
||||||
keys,
|
keys,
|
||||||
content: { text: 'Hello world!' },
|
content: { text: 'Hello world!' },
|
||||||
timestamp: 1652037377204,
|
when: 1652037377204,
|
||||||
type: 'xy',
|
type: 'xy',
|
||||||
previous: [],
|
prev: [],
|
||||||
hmacKey,
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/shorter than 3/,
|
/shorter than 3/,
|
||||||
|
@ -86,19 +78,17 @@ tape('invalid type too short', function (t) {
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
tape('invalid type too long', function (t) {
|
tape('invalid type too long', (t) => {
|
||||||
const keys = generateKeypair('alice')
|
const keys = generateKeypair('alice')
|
||||||
const hmacKey = null
|
|
||||||
|
|
||||||
t.throws(
|
t.throws(
|
||||||
() => {
|
() => {
|
||||||
dagfeed.newNativeMsg({
|
FeedV1.create({
|
||||||
keys,
|
keys,
|
||||||
content: { text: 'Hello world!' },
|
content: { text: 'Hello world!' },
|
||||||
timestamp: 1652037377204,
|
when: 1652037377204,
|
||||||
type: 'a'.repeat(120),
|
type: 'a'.repeat(120),
|
||||||
previous: [],
|
prev: [],
|
||||||
hmacKey,
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/100\+ characters long/,
|
/100\+ characters long/,
|
||||||
|
|
|
@ -1,22 +1,20 @@
|
||||||
const tape = require('tape')
|
const tape = require('tape')
|
||||||
const base58 = require('bs58')
|
const base58 = require('bs58')
|
||||||
const dagfeed = require('../lib/feed-v1')
|
const FeedV1 = require('../lib/feed-v1')
|
||||||
const { generateKeypair } = require('./util')
|
const { generateKeypair } = require('./util')
|
||||||
|
|
||||||
tape('validate 1st msg', (t) => {
|
tape('validate 1st msg', (t) => {
|
||||||
const keys = generateKeypair('alice')
|
const keys = generateKeypair('alice')
|
||||||
const hmacKey = null
|
|
||||||
|
|
||||||
const nmsg1 = dagfeed.newNativeMsg({
|
const msg = FeedV1.create({
|
||||||
keys,
|
keys,
|
||||||
content: { text: 'Hello world!' },
|
content: { text: 'Hello world!' },
|
||||||
type: 'post',
|
type: 'post',
|
||||||
previous: [],
|
prev: [],
|
||||||
timestamp: 1652030001000,
|
when: 1652030001000,
|
||||||
hmacKey,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
dagfeed.validate(nmsg1, null, null, (err) => {
|
FeedV1.validate(msg, [], (err) => {
|
||||||
if (err) console.log(err)
|
if (err) console.log(err)
|
||||||
t.error(err, 'valid 1st msg')
|
t.error(err, 'valid 1st msg')
|
||||||
t.end()
|
t.end()
|
||||||
|
@ -25,28 +23,27 @@ tape('validate 1st msg', (t) => {
|
||||||
|
|
||||||
tape('validate 2nd msg with existing nativeMsg', (t) => {
|
tape('validate 2nd msg with existing nativeMsg', (t) => {
|
||||||
const keys = generateKeypair('alice')
|
const keys = generateKeypair('alice')
|
||||||
const hmacKey = null
|
|
||||||
|
|
||||||
const nmsg1 = dagfeed.newNativeMsg({
|
const msg1 = FeedV1.create({
|
||||||
keys,
|
keys,
|
||||||
content: { text: 'Hello world!' },
|
content: { text: 'Hello world!' },
|
||||||
type: 'post',
|
type: 'post',
|
||||||
previous: [],
|
prev: [],
|
||||||
timestamp: 1652030001000,
|
when: 1652030001000,
|
||||||
hmacKey,
|
|
||||||
})
|
})
|
||||||
const msgKey1 = dagfeed.getMsgId(nmsg1)
|
const msgHash1 = FeedV1.getMsgHash(msg1)
|
||||||
|
|
||||||
const nmsg2 = dagfeed.newNativeMsg({
|
const msg2 = FeedV1.create({
|
||||||
keys,
|
keys,
|
||||||
content: { text: 'Hello world!' },
|
content: { text: 'Hello world!' },
|
||||||
type: 'post',
|
type: 'post',
|
||||||
previous: [msgKey1],
|
prev: [msg1],
|
||||||
timestamp: 1652030002000,
|
when: 1652030002000,
|
||||||
hmacKey,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
dagfeed.validate(nmsg2, [nmsg1], null, (err) => {
|
const existing = new Map()
|
||||||
|
existing.set(msgHash1, msg1)
|
||||||
|
FeedV1.validate(msg2, existing, (err) => {
|
||||||
if (err) console.log(err)
|
if (err) console.log(err)
|
||||||
t.error(err, 'valid 2nd msg')
|
t.error(err, 'valid 2nd msg')
|
||||||
t.end()
|
t.end()
|
||||||
|
@ -55,60 +52,28 @@ tape('validate 2nd msg with existing nativeMsg', (t) => {
|
||||||
|
|
||||||
tape('validate 2nd msg with existing msgId', (t) => {
|
tape('validate 2nd msg with existing msgId', (t) => {
|
||||||
const keys = generateKeypair('alice')
|
const keys = generateKeypair('alice')
|
||||||
const hmacKey = null
|
|
||||||
|
|
||||||
const nmsg1 = dagfeed.newNativeMsg({
|
const msg1 = FeedV1.create({
|
||||||
keys,
|
keys,
|
||||||
content: { text: 'Hello world!' },
|
content: { text: 'Hello world!' },
|
||||||
type: 'post',
|
type: 'post',
|
||||||
previous: [],
|
prev: [],
|
||||||
timestamp: 1652030001000,
|
when: 1652030001000,
|
||||||
hmacKey,
|
|
||||||
})
|
})
|
||||||
const msgKey1 = dagfeed.getMsgId(nmsg1)
|
const msgKey1 = FeedV1.getMsgId(msg1)
|
||||||
|
const msgHash1 = FeedV1.getMsgHash(msg1)
|
||||||
|
|
||||||
const nmsg2 = dagfeed.newNativeMsg({
|
const msg2 = FeedV1.create({
|
||||||
keys,
|
keys,
|
||||||
content: { text: 'Hello world!' },
|
content: { text: 'Hello world!' },
|
||||||
type: 'post',
|
type: 'post',
|
||||||
previous: [msgKey1],
|
prev: [msg1],
|
||||||
timestamp: 1652030002000,
|
when: 1652030002000,
|
||||||
hmacKey,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
dagfeed.validate(nmsg2, [msgKey1], null, (err) => {
|
const existing = new Map()
|
||||||
if (err) console.log(err)
|
existing.set(msgHash1, msg1)
|
||||||
t.error(err, 'valid 2nd msg')
|
FeedV1.validate(msg2, existing, (err) => {
|
||||||
t.end()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
tape('validate 2nd msg with existing msgId in a Set', (t) => {
|
|
||||||
const keys = generateKeypair('alice')
|
|
||||||
const hmacKey = null
|
|
||||||
|
|
||||||
const nmsg1 = dagfeed.newNativeMsg({
|
|
||||||
keys,
|
|
||||||
content: { text: 'Hello world!' },
|
|
||||||
type: 'post',
|
|
||||||
previous: [],
|
|
||||||
timestamp: 1652030001000,
|
|
||||||
hmacKey,
|
|
||||||
})
|
|
||||||
const msgId1 = dagfeed.getMsgHash(nmsg1)
|
|
||||||
|
|
||||||
const nmsg2 = dagfeed.newNativeMsg({
|
|
||||||
keys,
|
|
||||||
content: { text: 'Hello world!' },
|
|
||||||
type: 'post',
|
|
||||||
previous: [msgId1],
|
|
||||||
timestamp: 1652030002000,
|
|
||||||
hmacKey,
|
|
||||||
})
|
|
||||||
|
|
||||||
const existing = new Set([msgId1])
|
|
||||||
|
|
||||||
dagfeed.validate(nmsg2, existing, null, (err) => {
|
|
||||||
if (err) console.log(err)
|
if (err) console.log(err)
|
||||||
t.error(err, 'valid 2nd msg')
|
t.error(err, 'valid 2nd msg')
|
||||||
t.end()
|
t.end()
|
||||||
|
@ -117,32 +82,27 @@ tape('validate 2nd msg with existing msgId in a Set', (t) => {
|
||||||
|
|
||||||
tape('validate 2nd msg with existing KVT', (t) => {
|
tape('validate 2nd msg with existing KVT', (t) => {
|
||||||
const keys = generateKeypair('alice')
|
const keys = generateKeypair('alice')
|
||||||
const hmacKey = null
|
|
||||||
|
|
||||||
const nmsg1 = dagfeed.newNativeMsg({
|
const msg1 = FeedV1.create({
|
||||||
keys,
|
keys,
|
||||||
content: { text: 'Hello world!' },
|
content: { text: 'Hello world!' },
|
||||||
type: 'post',
|
type: 'post',
|
||||||
previous: [],
|
prev: [],
|
||||||
timestamp: 1652030001000,
|
when: 1652030001000,
|
||||||
hmacKey,
|
|
||||||
})
|
})
|
||||||
const kvt1 = {
|
const msgHash1 = FeedV1.getMsgHash(msg1)
|
||||||
key: dagfeed.getMsgId(nmsg1),
|
|
||||||
value: dagfeed.fromNativeMsg(nmsg1),
|
|
||||||
timestamp: Date.now(),
|
|
||||||
}
|
|
||||||
|
|
||||||
const nmsg2 = dagfeed.newNativeMsg({
|
const msg2 = FeedV1.create({
|
||||||
keys,
|
keys,
|
||||||
content: { text: 'Hello world!' },
|
content: { text: 'Hello world!' },
|
||||||
type: 'post',
|
type: 'post',
|
||||||
previous: [kvt1.key],
|
prev: [msg1],
|
||||||
timestamp: 1652030002000,
|
when: 1652030002000,
|
||||||
hmacKey,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
dagfeed.validate(nmsg2, [kvt1], null, (err) => {
|
const existing = new Map()
|
||||||
|
existing.set(msgHash1, msg1)
|
||||||
|
FeedV1.validate(msg2,existing, (err) => {
|
||||||
if (err) console.log(err)
|
if (err) console.log(err)
|
||||||
t.error(err, 'valid 2nd msg')
|
t.error(err, 'valid 2nd msg')
|
||||||
t.end()
|
t.end()
|
||||||
|
@ -151,37 +111,37 @@ tape('validate 2nd msg with existing KVT', (t) => {
|
||||||
|
|
||||||
tape('validate 2nd forked msg', (t) => {
|
tape('validate 2nd forked msg', (t) => {
|
||||||
const keys = generateKeypair('alice')
|
const keys = generateKeypair('alice')
|
||||||
const hmacKey = null
|
|
||||||
|
|
||||||
const nmsg1 = dagfeed.newNativeMsg({
|
const msg1 = FeedV1.create({
|
||||||
keys,
|
keys,
|
||||||
content: { text: 'Hello world!' },
|
content: { text: 'Hello world!' },
|
||||||
type: 'post',
|
type: 'post',
|
||||||
previous: [],
|
prev: [],
|
||||||
timestamp: 1652030001000,
|
when: 1652030001000,
|
||||||
hmacKey,
|
|
||||||
})
|
})
|
||||||
const msgKey1 = dagfeed.getMsgId(nmsg1)
|
const msgHash1 = FeedV1.getMsgHash(msg1)
|
||||||
|
|
||||||
const nmsg2A = dagfeed.newNativeMsg({
|
const msg2A = FeedV1.create({
|
||||||
keys,
|
keys,
|
||||||
content: { text: 'Hello world!' },
|
content: { text: 'Hello world!' },
|
||||||
type: 'post',
|
type: 'post',
|
||||||
previous: [msgKey1],
|
prev: [msg1],
|
||||||
timestamp: 1652030002000,
|
when: 1652030002000,
|
||||||
hmacKey,
|
|
||||||
})
|
})
|
||||||
|
const msgHash2A = FeedV1.getMsgHash(msg2A)
|
||||||
|
|
||||||
const nmsg2B = dagfeed.newNativeMsg({
|
const msg2B = FeedV1.create({
|
||||||
keys,
|
keys,
|
||||||
content: { text: 'Hello world!' },
|
content: { text: 'Hello world!' },
|
||||||
type: 'post',
|
type: 'post',
|
||||||
previous: [msgKey1],
|
prev: [msg1],
|
||||||
timestamp: 1652030003000,
|
when: 1652030003000,
|
||||||
hmacKey,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
dagfeed.validate(nmsg2B, [nmsg1, nmsg2A], null, (err) => {
|
const existing = new Map()
|
||||||
|
existing.set(msgHash1, msg1)
|
||||||
|
existing.set(msgHash2A, msg2A)
|
||||||
|
FeedV1.validate(msg2B, existing, (err) => {
|
||||||
if (err) console.log(err)
|
if (err) console.log(err)
|
||||||
t.error(err, 'valid 2nd forked msg')
|
t.error(err, 'valid 2nd forked msg')
|
||||||
t.end()
|
t.end()
|
||||||
|
@ -190,33 +150,34 @@ tape('validate 2nd forked msg', (t) => {
|
||||||
|
|
||||||
tape('invalid msg with unknown previous', (t) => {
|
tape('invalid msg with unknown previous', (t) => {
|
||||||
const keys = generateKeypair('alice')
|
const keys = generateKeypair('alice')
|
||||||
const hmacKey = null
|
|
||||||
|
|
||||||
const nmsg1 = dagfeed.newNativeMsg({
|
const msg1 = FeedV1.create({
|
||||||
keys,
|
keys,
|
||||||
content: { text: 'Hello world!' },
|
content: { text: 'Hello world!' },
|
||||||
type: 'post',
|
type: 'post',
|
||||||
previous: [],
|
prev: [],
|
||||||
timestamp: 1652030001000,
|
when: 1652030001000,
|
||||||
hmacKey,
|
|
||||||
})
|
})
|
||||||
|
const msgHash1 = FeedV1.getMsgHash(msg1)
|
||||||
|
|
||||||
const fakeMsgKey1 = base58.encode(Buffer.alloc(16).fill(42))
|
const fakeMsgKey1 = base58.encode(Buffer.alloc(16).fill(42))
|
||||||
|
|
||||||
const nmsg2 = dagfeed.newNativeMsg({
|
const msg2 = FeedV1.create({
|
||||||
keys,
|
keys,
|
||||||
content: { text: 'Hello world!' },
|
content: { text: 'Hello world!' },
|
||||||
type: 'post',
|
type: 'post',
|
||||||
previous: [fakeMsgKey1],
|
prev: [msg1],
|
||||||
timestamp: 1652030002000,
|
when: 1652030002000,
|
||||||
hmacKey,
|
|
||||||
})
|
})
|
||||||
|
msg2.metadata.prev = [fakeMsgKey1]
|
||||||
|
|
||||||
dagfeed.validate(nmsg2, [nmsg1], null, (err) => {
|
const existing = new Map()
|
||||||
|
existing.set(msgHash1, msg1)
|
||||||
|
FeedV1.validate(msg2, existing, (err) => {
|
||||||
t.ok(err, 'invalid 2nd msg throws')
|
t.ok(err, 'invalid 2nd msg throws')
|
||||||
t.match(
|
t.match(
|
||||||
err.message,
|
err.message,
|
||||||
/previous .+ is not a known message ID/,
|
/prev .+ is not locally known/,
|
||||||
'invalid 2nd msg description'
|
'invalid 2nd msg description'
|
||||||
)
|
)
|
||||||
t.end()
|
t.end()
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
const test = require('tape')
|
|
||||||
const ssbKeys = require('ssb-keys')
|
|
||||||
const path = require('path')
|
|
||||||
const os = require('os')
|
|
||||||
const rimraf = require('rimraf')
|
|
||||||
const SecretStack = require('secret-stack')
|
|
||||||
const caps = require('ssb-caps')
|
|
||||||
const p = require('util').promisify
|
|
||||||
|
|
||||||
const DIR = path.join(os.tmpdir(), 'ppppp-db-filter-as-array')
|
|
||||||
rimraf.sync(DIR)
|
|
||||||
|
|
||||||
test('filterAsArray', async (t) => {
|
|
||||||
const ssb = SecretStack({ appKey: caps.shs })
|
|
||||||
.use(require('../'))
|
|
||||||
.use(require('ssb-classic'))
|
|
||||||
.call(null, {
|
|
||||||
keys: ssbKeys.generate('ed25519', 'alice'),
|
|
||||||
path: DIR,
|
|
||||||
})
|
|
||||||
|
|
||||||
await ssb.db.loaded()
|
|
||||||
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
await p(ssb.db.create)({
|
|
||||||
feedFormat: 'classic',
|
|
||||||
content:
|
|
||||||
i % 2 === 0
|
|
||||||
? { type: 'post', text: 'hello ' + i }
|
|
||||||
: { type: 'about', about: ssb.id, name: 'Mr. #' + i },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const results = ssb.db
|
|
||||||
.filterAsArray((msg) => msg.value.content.type === 'post')
|
|
||||||
.map((msg) => msg.value.content.text)
|
|
||||||
|
|
||||||
t.deepEqual(
|
|
||||||
results,
|
|
||||||
['hello 0', 'hello 2', 'hello 4', 'hello 6', 'hello 8'],
|
|
||||||
'queried posts'
|
|
||||||
)
|
|
||||||
|
|
||||||
await p(ssb.close)(true)
|
|
||||||
})
|
|
|
@ -1,49 +0,0 @@
|
||||||
const test = require('tape')
|
|
||||||
const ssbKeys = require('ssb-keys')
|
|
||||||
const path = require('path')
|
|
||||||
const os = require('os')
|
|
||||||
const rimraf = require('rimraf')
|
|
||||||
const SecretStack = require('secret-stack')
|
|
||||||
const caps = require('ssb-caps')
|
|
||||||
const p = require('util').promisify
|
|
||||||
|
|
||||||
const DIR = path.join(os.tmpdir(), 'ppppp-db-filter-as-iterator')
|
|
||||||
rimraf.sync(DIR)
|
|
||||||
|
|
||||||
test('filterAsIterator', async (t) => {
|
|
||||||
const ssb = SecretStack({ appKey: caps.shs })
|
|
||||||
.use(require('../'))
|
|
||||||
.use(require('ssb-classic'))
|
|
||||||
.call(null, {
|
|
||||||
keys: ssbKeys.generate('ed25519', 'alice'),
|
|
||||||
path: DIR,
|
|
||||||
})
|
|
||||||
|
|
||||||
await ssb.db.loaded()
|
|
||||||
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
await p(ssb.db.create)({
|
|
||||||
feedFormat: 'classic',
|
|
||||||
content:
|
|
||||||
i % 2 === 0
|
|
||||||
? { type: 'post', text: 'hello ' + i }
|
|
||||||
: { type: 'about', about: ssb.id, name: 'Mr. #' + i },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const iterator = ssb.db.filterAsIterator(
|
|
||||||
(msg) => msg.value.content.type === 'post'
|
|
||||||
)
|
|
||||||
const results = []
|
|
||||||
for (const msg of iterator) {
|
|
||||||
results.push(msg.value.content.text)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.deepEqual(
|
|
||||||
results,
|
|
||||||
['hello 0', 'hello 2', 'hello 4', 'hello 6', 'hello 8'],
|
|
||||||
'queried posts'
|
|
||||||
)
|
|
||||||
|
|
||||||
await p(ssb.close)(true)
|
|
||||||
})
|
|
|
@ -1,48 +0,0 @@
|
||||||
const test = require('tape')
|
|
||||||
const ssbKeys = require('ssb-keys')
|
|
||||||
const path = require('path')
|
|
||||||
const rimraf = require('rimraf')
|
|
||||||
const os = require('os')
|
|
||||||
const SecretStack = require('secret-stack')
|
|
||||||
const caps = require('ssb-caps')
|
|
||||||
const pull = require('pull-stream')
|
|
||||||
const p = require('util').promisify
|
|
||||||
|
|
||||||
const DIR = path.join(os.tmpdir(), 'ppppp-db-filter-as-pull-stream')
|
|
||||||
rimraf.sync(DIR)
|
|
||||||
|
|
||||||
test('filterAsPullStream', async (t) => {
|
|
||||||
const ssb = SecretStack({ appKey: caps.shs })
|
|
||||||
.use(require('../'))
|
|
||||||
.use(require('ssb-classic'))
|
|
||||||
.call(null, {
|
|
||||||
keys: ssbKeys.generate('ed25519', 'alice'),
|
|
||||||
path: DIR,
|
|
||||||
})
|
|
||||||
|
|
||||||
await ssb.db.loaded()
|
|
||||||
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
await p(ssb.db.create)({
|
|
||||||
feedFormat: 'classic',
|
|
||||||
content:
|
|
||||||
i % 2 === 0
|
|
||||||
? { type: 'post', text: 'hello ' + i }
|
|
||||||
: { type: 'about', about: ssb.id, name: 'Mr. #' + i },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const results = await pull(
|
|
||||||
ssb.db.filterAsPullStream((msg) => msg.value.content.type === 'post'),
|
|
||||||
pull.map((msg) => msg.value.content.text),
|
|
||||||
pull.collectAsPromise()
|
|
||||||
)
|
|
||||||
|
|
||||||
t.deepEqual(
|
|
||||||
results,
|
|
||||||
['hello 0', 'hello 2', 'hello 4', 'hello 6', 'hello 8'],
|
|
||||||
'queried posts'
|
|
||||||
)
|
|
||||||
|
|
||||||
await p(ssb.close)(true)
|
|
||||||
})
|
|
|
@ -1,48 +0,0 @@
|
||||||
const test = require('tape')
|
|
||||||
const ssbKeys = require('ssb-keys')
|
|
||||||
const path = require('path')
|
|
||||||
const rimraf = require('rimraf')
|
|
||||||
const os = require('os')
|
|
||||||
const SecretStack = require('secret-stack')
|
|
||||||
const caps = require('ssb-caps')
|
|
||||||
const p = require('util').promisify
|
|
||||||
|
|
||||||
const DIR = path.join(os.tmpdir(), 'ppppp-db-for-each')
|
|
||||||
rimraf.sync(DIR)
|
|
||||||
|
|
||||||
test('forEach', async (t) => {
|
|
||||||
const ssb = SecretStack({ appKey: caps.shs })
|
|
||||||
.use(require('../'))
|
|
||||||
.use(require('ssb-classic'))
|
|
||||||
.call(null, {
|
|
||||||
keys: ssbKeys.generate('ed25519', 'alice'),
|
|
||||||
path: DIR,
|
|
||||||
})
|
|
||||||
|
|
||||||
await ssb.db.loaded()
|
|
||||||
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
await p(ssb.db.create)({
|
|
||||||
feedFormat: 'classic',
|
|
||||||
content:
|
|
||||||
i % 2 === 0
|
|
||||||
? { type: 'post', text: 'hello ' + i }
|
|
||||||
: { type: 'about', about: ssb.id, name: 'Mr. #' + i },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const results = []
|
|
||||||
ssb.db.forEach((msg) => {
|
|
||||||
if (msg.value.content.type === 'post') {
|
|
||||||
results.push(msg.value.content.text)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.deepEqual(
|
|
||||||
results,
|
|
||||||
['hello 0', 'hello 2', 'hello 4', 'hello 6', 'hello 8'],
|
|
||||||
'queried posts'
|
|
||||||
)
|
|
||||||
|
|
||||||
await p(ssb.close)(true)
|
|
||||||
})
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
const test = require('tape')
|
||||||
|
const path = require('path')
|
||||||
|
const os = require('os')
|
||||||
|
const rimraf = require('rimraf')
|
||||||
|
const SecretStack = require('secret-stack')
|
||||||
|
const caps = require('ssb-caps')
|
||||||
|
const p = require('util').promisify
|
||||||
|
const { generateKeypair } = require('./util')
|
||||||
|
|
||||||
|
const DIR = path.join(os.tmpdir(), 'ppppp-db-msgs-iter')
|
||||||
|
rimraf.sync(DIR)
|
||||||
|
|
||||||
|
test('msgs() iterator', async (t) => {
|
||||||
|
const keys = generateKeypair('alice')
|
||||||
|
const peer = SecretStack({ appKey: caps.shs })
|
||||||
|
.use(require('../'))
|
||||||
|
.call(null, { keys, path: DIR })
|
||||||
|
|
||||||
|
await peer.db.loaded()
|
||||||
|
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
await p(peer.db.create)({
|
||||||
|
type: i % 2 === 0 ? 'post' : 'about',
|
||||||
|
content:
|
||||||
|
i % 2 === 0
|
||||||
|
? { text: 'hello ' + i }
|
||||||
|
: { about: peer.id, name: 'Mr. #' + i },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const posts = []
|
||||||
|
const abouts = []
|
||||||
|
for (const msg of peer.db.msgs()) {
|
||||||
|
if (msg.metadata.type === 'post') posts.push(msg.content.text)
|
||||||
|
else if (msg.metadata.type === 'about') abouts.push(msg.content.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.deepEqual(posts, ['hello 0', 'hello 2', 'hello 4'], 'queried posts')
|
||||||
|
t.deepEqual(abouts, ['Mr. #1', 'Mr. #3', 'Mr. #5'], 'queried abouts')
|
||||||
|
|
||||||
|
await p(peer.close)(true)
|
||||||
|
})
|
|
@ -1,45 +0,0 @@
|
||||||
const test = require('tape')
|
|
||||||
const ssbKeys = require('ssb-keys')
|
|
||||||
const path = require('path')
|
|
||||||
const rimraf = require('rimraf')
|
|
||||||
const os = require('os')
|
|
||||||
const SecretStack = require('secret-stack')
|
|
||||||
const caps = require('ssb-caps')
|
|
||||||
const p = require('util').promisify
|
|
||||||
|
|
||||||
const DIR = path.join(os.tmpdir(), 'ppppp-db-on-msg-added')
|
|
||||||
rimraf.sync(DIR)
|
|
||||||
|
|
||||||
test('onMsgAdded', async (t) => {
|
|
||||||
const ssb = SecretStack({ appKey: caps.shs })
|
|
||||||
.use(require('../'))
|
|
||||||
.use(require('ssb-classic'))
|
|
||||||
.call(null, {
|
|
||||||
keys: ssbKeys.generate('ed25519', 'alice'),
|
|
||||||
path: DIR,
|
|
||||||
})
|
|
||||||
|
|
||||||
await ssb.db.loaded()
|
|
||||||
|
|
||||||
const listened = []
|
|
||||||
var remove = ssb.db.onMsgAdded((ev) => {
|
|
||||||
listened.push(ev)
|
|
||||||
})
|
|
||||||
|
|
||||||
const msg1 = await p(ssb.db.create)({
|
|
||||||
feedFormat: 'classic',
|
|
||||||
content: { type: 'post', text: 'I am hungry' },
|
|
||||||
})
|
|
||||||
t.equal(msg1.value.content.text, 'I am hungry', 'msg1 text correct')
|
|
||||||
|
|
||||||
await p(setTimeout)(500)
|
|
||||||
|
|
||||||
t.equal(listened.length, 1)
|
|
||||||
t.deepEquals(Object.keys(listened[0]), ['kvt', 'nativeMsg', 'feedFormat'])
|
|
||||||
t.deepEquals(listened[0].kvt, msg1)
|
|
||||||
t.deepEquals(listened[0].nativeMsg, msg1.value)
|
|
||||||
t.equals(listened[0].feedFormat, 'classic')
|
|
||||||
|
|
||||||
remove()
|
|
||||||
await p(ssb.close)(true)
|
|
||||||
})
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
const test = require('tape')
|
||||||
|
const path = require('path')
|
||||||
|
const rimraf = require('rimraf')
|
||||||
|
const os = require('os')
|
||||||
|
const SecretStack = require('secret-stack')
|
||||||
|
const caps = require('ssb-caps')
|
||||||
|
const p = require('util').promisify
|
||||||
|
const { generateKeypair } = require('./util')
|
||||||
|
|
||||||
|
const DIR = path.join(os.tmpdir(), 'ppppp-db-on-msg-added')
|
||||||
|
rimraf.sync(DIR)
|
||||||
|
|
||||||
|
test('onRecordAdded', async (t) => {
|
||||||
|
const keys = generateKeypair('alice')
|
||||||
|
const peer = SecretStack({ appKey: caps.shs })
|
||||||
|
.use(require('../'))
|
||||||
|
.call(null, { keys, path: DIR })
|
||||||
|
|
||||||
|
await peer.db.loaded()
|
||||||
|
|
||||||
|
const listened = []
|
||||||
|
var remove = peer.db.onRecordAdded((ev) => {
|
||||||
|
listened.push(ev)
|
||||||
|
})
|
||||||
|
|
||||||
|
const rec1 = await p(peer.db.create)({
|
||||||
|
type: 'post',
|
||||||
|
content: { text: 'I am hungry' },
|
||||||
|
})
|
||||||
|
t.equal(rec1.msg.content.text, 'I am hungry', 'msg1 text correct')
|
||||||
|
|
||||||
|
await p(setTimeout)(500)
|
||||||
|
|
||||||
|
t.equal(listened.length, 1)
|
||||||
|
t.deepEquals(listened, [rec1])
|
||||||
|
|
||||||
|
remove()
|
||||||
|
await p(peer.close)(true)
|
||||||
|
})
|
|
@ -0,0 +1,39 @@
|
||||||
|
const test = require('tape')
|
||||||
|
const path = require('path')
|
||||||
|
const os = require('os')
|
||||||
|
const rimraf = require('rimraf')
|
||||||
|
const SecretStack = require('secret-stack')
|
||||||
|
const caps = require('ssb-caps')
|
||||||
|
const p = require('util').promisify
|
||||||
|
const { generateKeypair } = require('./util')
|
||||||
|
|
||||||
|
const DIR = path.join(os.tmpdir(), 'ppppp-db-records-iter')
|
||||||
|
rimraf.sync(DIR)
|
||||||
|
|
||||||
|
test('records() iterator', async (t) => {
|
||||||
|
const keys = generateKeypair('alice')
|
||||||
|
const peer = SecretStack({ appKey: caps.shs })
|
||||||
|
.use(require('../'))
|
||||||
|
.call(null, { keys, path: DIR })
|
||||||
|
|
||||||
|
await peer.db.loaded()
|
||||||
|
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
await p(peer.db.create)({
|
||||||
|
type: i % 2 === 0 ? 'post' : 'about',
|
||||||
|
content:
|
||||||
|
i % 2 === 0
|
||||||
|
? { text: 'hello ' + i }
|
||||||
|
: { about: peer.id, name: 'Mr. #' + i },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let count = 0
|
||||||
|
for (const rec of peer.db.records()) {
|
||||||
|
t.true(rec.misc.size > rec.msg.metadata.size)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
t.equals(count, 6)
|
||||||
|
|
||||||
|
await p(peer.close)(true)
|
||||||
|
})
|
|
@ -5,7 +5,7 @@ const base58 = require('bs58')
|
||||||
function generateKeypair(seed) {
|
function generateKeypair(seed) {
|
||||||
const keys = ssbKeys.generate('ed25519', seed, 'buttwoo-v1')
|
const keys = ssbKeys.generate('ed25519', seed, 'buttwoo-v1')
|
||||||
const { data } = SSBURI.decompose(keys.id)
|
const { data } = SSBURI.decompose(keys.id)
|
||||||
keys.id = `ssb:feed/dag/${base58.encode(Buffer.from(data, 'base64'))}`
|
keys.id = `ppppp:feed/v1/${base58.encode(Buffer.from(data, 'base64'))}`
|
||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue