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) {
|
||||
const dot = str.indexOf('.')
|
||||
return Buffer.from(str.slice(0, dot), 'base64')
|
||||
}
|
||||
|
||||
function decrypt(msg, ssb, config) {
|
||||
const { author, previous, content } = msg.value
|
||||
if (typeof content !== 'string') return msg
|
||||
/**
|
||||
* @param {Rec} rec
|
||||
* @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)
|
||||
if (!encryptionFormat) return msg
|
||||
|
||||
const feedFormat = ssb.db.findFeedFormatForAuthor(author)
|
||||
if (!feedFormat) return msg
|
||||
const encryptionFormat = peer.db.findEncryptionFormatFor(content)
|
||||
if (!encryptionFormat) return rec
|
||||
|
||||
// Decrypt
|
||||
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)
|
||||
if (!plaintextBuf) return msg
|
||||
if (!plaintextBuf) return rec
|
||||
|
||||
// Reconstruct KVT in JS encoding
|
||||
const nativeMsg = feedFormat.toNativeMsg(msg.value, 'js')
|
||||
// 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'
|
||||
)
|
||||
const msgDecrypted = FeedV1.fromPlaintextBuffer(plaintextBuf, msgEncrypted)
|
||||
|
||||
return {
|
||||
key: msg.key,
|
||||
value: msgVal,
|
||||
timestamp: msg.timestamp,
|
||||
meta: {
|
||||
id: rec.id,
|
||||
msg: msgDecrypted,
|
||||
received: rec.received,
|
||||
misc: {
|
||||
...rec.misc,
|
||||
private: true,
|
||||
originalContent: content,
|
||||
encryptionFormat: encryptionFormat.name,
|
||||
|
@ -41,16 +46,16 @@ function decrypt(msg, ssb, config) {
|
|||
}
|
||||
}
|
||||
|
||||
function reEncrypt(msg) {
|
||||
function reEncrypt(rec) {
|
||||
return {
|
||||
key: msg.key,
|
||||
value: { ...msg.value, content: msg.meta.originalContent },
|
||||
timestamp: msg.timestamp,
|
||||
...(msg.meta.size
|
||||
id: rec.id,
|
||||
msg: { ...rec.msg, content: rec.misc.originalContent },
|
||||
received: rec.received,
|
||||
...(rec.misc.size
|
||||
? {
|
||||
meta: {
|
||||
offset: msg.meta.offset,
|
||||
size: msg.meta.size,
|
||||
misc: {
|
||||
offset: rec.misc.offset,
|
||||
size: rec.misc.size,
|
||||
},
|
||||
}
|
||||
: null),
|
||||
|
|
|
@ -2,25 +2,54 @@ const blake3 = require('blake3')
|
|||
const base58 = require('bs58')
|
||||
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 sigBuf = base58.decode(signature)
|
||||
return blake3
|
||||
.hash(Buffer.concat([metadataBuf, sigBuf]))
|
||||
.subarray(0, 16)
|
||||
const sigBuf = base58.decode(sig)
|
||||
return blake3.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)
|
||||
}
|
||||
}
|
||||
|
||||
function getMsgId(nativeMsg) {
|
||||
const author = nativeMsg.metadata.author
|
||||
const type = nativeMsg.metadata.type
|
||||
const msgHash = getMsgHash(nativeMsg)
|
||||
return `ssb:message/dag/${author}/${type}/${msgHash}`
|
||||
/**
|
||||
* @param {Msg} msg
|
||||
* @returns {string}
|
||||
*/
|
||||
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 }
|
||||
|
|
|
@ -5,12 +5,7 @@
|
|||
const stringify = require('fast-json-stable-stringify')
|
||||
const ed25519 = require('ssb-keys/sodium')
|
||||
const base58 = require('bs58')
|
||||
const {
|
||||
stripAuthor,
|
||||
stripMsgKey,
|
||||
unstripMsgKey,
|
||||
unstripAuthor,
|
||||
} = require('./strip')
|
||||
const { stripAuthor, stripMsgKey } = require('./strip')
|
||||
const { getMsgId, getMsgHash } = require('./get-msg-id')
|
||||
const representContent = require('./represent-content')
|
||||
const {
|
||||
|
@ -22,18 +17,32 @@ const {
|
|||
validateOOOBatch,
|
||||
} = 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) {
|
||||
throw new Error('getSequence not supported for dagfeed')
|
||||
}
|
||||
|
||||
function isNativeMsg(x) {
|
||||
function isMsg(x) {
|
||||
return (
|
||||
typeof x === 'object' &&
|
||||
!!x &&
|
||||
|
@ -44,119 +53,92 @@ function isNativeMsg(x) {
|
|||
)
|
||||
}
|
||||
|
||||
function isAuthor(author) {
|
||||
function isFeedId(author) {
|
||||
if (typeof author !== 'string') return false
|
||||
return author.startsWith('ssb:feed/dag/')
|
||||
return author.startsWith('ppppp:feed/v1/')
|
||||
}
|
||||
|
||||
function toPlaintextBuffer(opts) {
|
||||
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
|
||||
if ((err = validateType(opts.type))) throw err
|
||||
if (opts.previous && !Array.isArray(opts.previous)) {
|
||||
// prettier-ignore
|
||||
throw new Error('opts.previous must be an array, but got ' + typeof opts.previous)
|
||||
}
|
||||
prevalidatePrev(opts.prev)
|
||||
|
||||
const [contentHash, contentSize] = representContent(opts.content)
|
||||
const nativeMsg = {
|
||||
metadata: {
|
||||
author: stripAuthor(opts.keys.id),
|
||||
type: opts.type,
|
||||
previous: (opts.previous ?? []).map(stripMsgKey),
|
||||
timestamp: +opts.timestamp,
|
||||
contentHash,
|
||||
contentSize,
|
||||
},
|
||||
const [proof, size] = representContent(opts.content)
|
||||
const depth = calculateDepth(opts.prev)
|
||||
const msg = {
|
||||
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: {
|
||||
author: stripAuthor(msgVal.author),
|
||||
type: msgVal.type ?? '',
|
||||
previous: (msgVal.previous ?? []).map(stripMsgKey),
|
||||
timestamp: msgVal.timestamp,
|
||||
contentHash: msgVal.contentHash,
|
||||
contentSize: msgVal.contentSize,
|
||||
depth,
|
||||
prev: summarizePrev(opts.prev),
|
||||
proof,
|
||||
size,
|
||||
type: opts.type,
|
||||
who: stripAuthor(opts.keys.id),
|
||||
when: +opts.when,
|
||||
},
|
||||
content: msgVal.content,
|
||||
signature: msgVal.signature,
|
||||
sig: '',
|
||||
}
|
||||
} 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 = {
|
||||
name,
|
||||
encodings,
|
||||
getMsgId,
|
||||
getFeedId,
|
||||
getSequence,
|
||||
isAuthor,
|
||||
isNativeMsg,
|
||||
isFeedId,
|
||||
isMsg,
|
||||
create,
|
||||
toPlaintextBuffer,
|
||||
newNativeMsg,
|
||||
fromNativeMsg,
|
||||
fromDecryptedNativeMsg,
|
||||
toNativeMsg,
|
||||
fromPlaintextBuffer,
|
||||
validate,
|
||||
validateOOO,
|
||||
validateBatch,
|
||||
|
|
|
@ -2,6 +2,10 @@ const blake3 = require('blake3')
|
|||
const base58 = require('bs58')
|
||||
const stringify = require('fast-json-stable-stringify')
|
||||
|
||||
/**
|
||||
* @param {any} content
|
||||
* @returns {[string, number]}
|
||||
*/
|
||||
function representContent(content) {
|
||||
const contentBuf = Buffer.from(stringify(content), 'utf8')
|
||||
const hash = base58.encode(blake3.hash(contentBuf).subarray(0, 16))
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
const { getMsgHash } = require('./get-msg-id')
|
||||
|
||||
function stripMsgKey(msgKey) {
|
||||
if (typeof msgKey === 'object') return stripMsgKey(msgKey.key)
|
||||
if (msgKey.startsWith('ssb:message/dag/')) {
|
||||
if (typeof msgKey === 'object') {
|
||||
if (msgKey.key) return stripMsgKey(msgKey.key)
|
||||
else return getMsgHash(msgKey)
|
||||
}
|
||||
if (msgKey.startsWith('ppppp:message/v1/')) {
|
||||
const parts = msgKey.split('/')
|
||||
return parts[parts.length - 1]
|
||||
} 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) {
|
||||
const withoutPrefix = id.replace('ssb:feed/dag/', '')
|
||||
const withoutPrefix = id.replace('ppppp:feed/v1/', '')
|
||||
return withoutPrefix.split('/')[0]
|
||||
}
|
||||
|
||||
function unstripAuthor(nativeMsg) {
|
||||
const { author, type } = nativeMsg.metadata
|
||||
return `ssb:feed/dag/${author}/${type}`
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
stripMsgKey,
|
||||
unstripMsgKey,
|
||||
stripAuthor,
|
||||
unstripAuthor,
|
||||
}
|
||||
|
|
|
@ -1,132 +1,122 @@
|
|||
const base58 = require('bs58')
|
||||
const ed25519 = require('ssb-keys/sodium')
|
||||
const stringify = require('fast-json-stable-stringify')
|
||||
const { stripMsgKey } = require('./strip')
|
||||
const { getMsgHash } = require('./get-msg-id')
|
||||
|
||||
function validateShape(nativeMsg) {
|
||||
if (!nativeMsg || typeof nativeMsg !== 'object') {
|
||||
return new Error('invalid message: not a dag msg')
|
||||
function validateShape(msg) {
|
||||
if (!msg || typeof msg !== 'object') {
|
||||
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')
|
||||
}
|
||||
if (typeof nativeMsg.metadata.author === 'undefined') {
|
||||
return new Error('invalid message: must have metadata.author')
|
||||
if (typeof msg.metadata.who === 'undefined') {
|
||||
return new Error('invalid message: must have metadata.who')
|
||||
}
|
||||
if (typeof nativeMsg.metadata.type === 'undefined') {
|
||||
return new Error('invalid message: must have metadata.sequence')
|
||||
if (typeof msg.metadata.depth === 'undefined') {
|
||||
return new Error('invalid message: must have metadata.depth')
|
||||
}
|
||||
if (typeof nativeMsg.metadata.previous === 'undefined') {
|
||||
return new Error('invalid message: must have metadata.previous')
|
||||
if (typeof msg.metadata.prev === 'undefined') {
|
||||
return new Error('invalid message: must have metadata.prev')
|
||||
}
|
||||
if (typeof nativeMsg.metadata.timestamp === 'undefined') {
|
||||
return new Error('invalid message: must have metadata.timestamp')
|
||||
if (typeof msg.metadata.proof === 'undefined') {
|
||||
return new Error('invalid message: must have metadata.proof')
|
||||
}
|
||||
if (typeof nativeMsg.metadata.contentHash === 'undefined') {
|
||||
return new Error('invalid message: must have metadata.contentHash')
|
||||
if (typeof msg.metadata.size === 'undefined') {
|
||||
return new Error('invalid message: must have metadata.size')
|
||||
}
|
||||
if (typeof nativeMsg.metadata.contentSize === 'undefined') {
|
||||
return new Error('invalid message: must have metadata.contentSize')
|
||||
}
|
||||
if (typeof nativeMsg.content === 'undefined') {
|
||||
if (typeof msg.content === 'undefined') {
|
||||
return new Error('invalid message: must have content')
|
||||
}
|
||||
if (typeof nativeMsg.signature === 'undefined') {
|
||||
return new Error('invalid message: must have signature')
|
||||
if (typeof msg.sig === 'undefined') {
|
||||
return new Error('invalid message: must have sig')
|
||||
}
|
||||
}
|
||||
|
||||
function validateAuthor(nativeMsg) {
|
||||
function validateWho(msg) {
|
||||
try {
|
||||
base58.decode(nativeMsg.metadata.author)
|
||||
base58.decode(msg.metadata.who)
|
||||
} 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) {
|
||||
const { signature } = nativeMsg
|
||||
if (typeof signature !== 'string') {
|
||||
return new Error('invalid message: must have signature as a string')
|
||||
function validateSignature(msg) {
|
||||
const { sig } = msg
|
||||
if (typeof sig !== 'string') {
|
||||
return new Error('invalid message: must have sig as a string')
|
||||
}
|
||||
try {
|
||||
base58.decode(signature)
|
||||
base58.decode(sig)
|
||||
} 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))
|
||||
if (signatureBuf.length !== 64) {
|
||||
const sigBuf = Buffer.from(base58.decode(sig))
|
||||
if (sigBuf.length !== 64) {
|
||||
// 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 signableBuf = Buffer.from(stringify(nativeMsg.metadata), 'utf8')
|
||||
const verified = ed25519.verify(publicKeyBuf, signatureBuf, signableBuf)
|
||||
const publicKeyBuf = Buffer.from(base58.decode(msg.metadata.who))
|
||||
const signableBuf = Buffer.from(stringify(msg.metadata), 'utf8')
|
||||
const verified = ed25519.verify(publicKeyBuf, sigBuf, signableBuf)
|
||||
if (!verified) {
|
||||
// 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) {
|
||||
if (!Array.isArray(nativeMsg.metadata.previous)) {
|
||||
function validatePrev(msg, existingMsgs) {
|
||||
if (!msg.metadata.prev || !msg.metadata.prev[Symbol.iterator]) {
|
||||
// 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) {
|
||||
if (typeof prevId !== 'string') {
|
||||
for (const p of msg.metadata.prev) {
|
||||
if (typeof p !== 'string') {
|
||||
// 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
|
||||
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 (!existingNativeMsgs.has(prevId)) {
|
||||
if (!existingMsgs.has(p)) {
|
||||
// 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
|
||||
} else {
|
||||
let found = false
|
||||
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) {
|
||||
const existingMsg = existingMsgs.get(p)
|
||||
|
||||
if (existingMsg.metadata.who !== msg.metadata.who) {
|
||||
// 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) {
|
||||
if (!Array.isArray(nativeMsg.metadata.previous)) {
|
||||
function validateFirstPrev(msg) {
|
||||
if (!Array.isArray(msg.metadata.prev)) {
|
||||
// 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
|
||||
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) {
|
||||
if (typeof nativeMsg.metadata.timestamp !== 'number') {
|
||||
function validateWhen(msg) {
|
||||
if (msg.metadata.when && typeof msg.metadata.when !== 'number') {
|
||||
// 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) {
|
||||
const { content } = nativeMsg
|
||||
function validateContent(msg) {
|
||||
// FIXME: if content exists, check it against `proof` and `size`
|
||||
// FIXME: if content does not exist, do nothing
|
||||
const { content } = msg
|
||||
if (!content) {
|
||||
return new Error('invalid message: must have content')
|
||||
}
|
||||
|
@ -159,51 +151,24 @@ function validateContent(nativeMsg) {
|
|||
}
|
||||
if (typeof content !== 'object' && typeof content !== 'string') {
|
||||
// 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) {
|
||||
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')
|
||||
// FIXME: validateDepth should be +1 of the max of prev depth
|
||||
|
||||
if (typeof hmacKey === 'string' && bytes.toString('base64') !== hmacKey) {
|
||||
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) {
|
||||
function validateSync(msg, existingMsgs) {
|
||||
let err
|
||||
if ((err = validateShape(nativeMsg))) return err
|
||||
if ((err = validateHmac(hmacKey))) return err
|
||||
if ((err = validateAuthor(nativeMsg))) return err
|
||||
if ((err = validateTimestamp(nativeMsg))) return err
|
||||
if (emptyExisting(existingNativeMsgs)) {
|
||||
if ((err = validateFirstPrevious(nativeMsg))) return err
|
||||
if ((err = validateShape(msg))) return err
|
||||
if ((err = validateWho(msg))) return err
|
||||
if ((err = validateWhen(msg))) return err
|
||||
if (msg.metadata.depth === 0) {
|
||||
if ((err = validateFirstPrev(msg))) return err
|
||||
} else {
|
||||
if ((err = validatePrevious(nativeMsg, existingNativeMsgs))) return err
|
||||
if ((err = validatePrev(msg, existingMsgs))) return err
|
||||
}
|
||||
if ((err = validateContent(nativeMsg))) return err
|
||||
if ((err = validateSignature(nativeMsg, hmacKey))) return err
|
||||
if ((err = validateContent(msg))) return err
|
||||
if ((err = validateSignature(msg))) return err
|
||||
}
|
||||
|
||||
// function validateOOOSync(nativeMsg, hmacKey) {
|
||||
|
@ -212,16 +177,15 @@ function validateSync(nativeMsg, existingNativeMsgs, hmacKey) {
|
|||
// if ((err = validateHmac(hmacKey))) return err
|
||||
// if ((err = validateAuthor(nativeMsg))) return err
|
||||
// if ((err = validateHash(nativeMsg))) return err
|
||||
// if ((err = validateTimestamp(nativeMsg))) return err
|
||||
// if ((err = validateOrder(nativeMsg))) return err
|
||||
// if ((err = validateContent(nativeMsg))) return err
|
||||
// if ((err = validateAsJSON(nativeMsg))) return err
|
||||
// if ((err = validateSignature(nativeMsg, hmacKey))) return err
|
||||
// }
|
||||
|
||||
function validate(nativeMsg, prevNativeMsg, hmacKey, cb) {
|
||||
function validate(msg, existingMsgs, cb) {
|
||||
let err
|
||||
if ((err = validateSync(nativeMsg, prevNativeMsg, hmacKey))) {
|
||||
if ((err = validateSync(msg, existingMsgs))) {
|
||||
return cb(err)
|
||||
}
|
||||
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 promisify = require('promisify-4loc')
|
||||
const Obz = require('obz')
|
||||
const FeedV1 = require('./feed-v1')
|
||||
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.init = function initMemDB(ssb, config) {
|
||||
const hmacKey = null
|
||||
const msgs = []
|
||||
const feedFormats = new Map()
|
||||
exports.init = function initDB(peer, config) {
|
||||
/** @type {Array<Rec>} */
|
||||
const recs = []
|
||||
const encryptionFormats = new Map()
|
||||
const onMsgAdded = Obz()
|
||||
const onRecordAdded = Obz()
|
||||
|
||||
const latestMsgPerFeed = {
|
||||
_map: new Map(), // feedId => nativeMsg
|
||||
preupdateFromKVT(kvtf, i) {
|
||||
const feedId = kvtf.feed ?? kvtf.value.author
|
||||
this._map.set(feedId, i)
|
||||
},
|
||||
commitAllPreupdates() {
|
||||
for (const i of this._map.values()) {
|
||||
if (typeof i === 'number') {
|
||||
this.updateFromKVT(msgs[i])
|
||||
}
|
||||
const msgsPerFeed = {
|
||||
_mapAll: new Map(), // who => Set<Msg>
|
||||
_mapTips: new Map(), // who => Set<Msg>
|
||||
_byHash: new Map(), // msgId => Msg // TODO: optimize space usage of this??
|
||||
update(msg, msgId) {
|
||||
const msgHash = FeedV1.getMsgHash(msgId ?? msg)
|
||||
const feedId = FeedV1.getFeedId(msg)
|
||||
const setAll = this._mapAll.get(feedId) ?? new Set()
|
||||
const setTips = this._mapTips.get(feedId) ?? new Set()
|
||||
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) {
|
||||
const feedId = kvtf.feed ?? kvtf.value.author
|
||||
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)
|
||||
getAll() {
|
||||
return this._byHash
|
||||
},
|
||||
update(feedId, nativeMsg) {
|
||||
this._map.set(feedId, nativeMsg)
|
||||
getTips(feedId) {
|
||||
return this._mapTips.get(feedId) ?? []
|
||||
},
|
||||
get(feedId) {
|
||||
return this._map.get(feedId) ?? null
|
||||
},
|
||||
has(feedId) {
|
||||
return this._map.has(feedId)
|
||||
},
|
||||
getAsKV(feedId, feedFormat) {
|
||||
const nativeMsg = this._map.get(feedId)
|
||||
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)
|
||||
deleteMsg(msg) {
|
||||
const feedId = FeedV1.getFeedId(msg)
|
||||
const msgHash = FeedV1.getMsgHash(msg)
|
||||
const setAll = this._mapAll.get(feedId)
|
||||
setAll.delete(msg)
|
||||
const setTips = this._mapTips.get(feedId)
|
||||
setTips.delete(msg)
|
||||
this._byHash.delete(msgHash)
|
||||
},
|
||||
}
|
||||
|
||||
const log = AAOL(path.join(config.path, 'memdb-log.bin'), {
|
||||
const log = AAOL(path.join(config.path, 'db.bin'), {
|
||||
cacheSize: 1,
|
||||
blockSize: 64 * 1024,
|
||||
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(() => {
|
||||
fn.apply(this, args)
|
||||
})
|
||||
})
|
||||
|
||||
const scannedLog = new ReadyGate()
|
||||
// setTimeout to let ssb.db.* secret-stack become available
|
||||
// setTimeout to let peer.db.* secret-stack become available
|
||||
setTimeout(() => {
|
||||
let i = -1
|
||||
log.stream({ offsets: true, values: true, sizes: true }).pipe(
|
||||
|
@ -113,59 +121,49 @@ exports.init = function initMemDB(ssb, config) {
|
|||
i += 1
|
||||
if (!value) {
|
||||
// deleted record
|
||||
msgs.push(null)
|
||||
recs.push({ misc: { offset, size, seq: i } })
|
||||
return
|
||||
}
|
||||
// TODO: for performance, dont decrypt on startup, instead decrypt on
|
||||
// demand, or decrypt in the background. Or then store the log with
|
||||
// decrypted msgs and only encrypt when moving it to the network.
|
||||
const msg = decrypt(value, ssb, config)
|
||||
msg.meta ??= {}
|
||||
msg.meta.offset = offset
|
||||
msg.meta.size = size
|
||||
msg.meta.seq = i
|
||||
msgs.push(msg)
|
||||
const rec = decrypt(value, peer, config)
|
||||
rec.misc ??= {}
|
||||
rec.misc.offset = offset
|
||||
rec.misc.size = size
|
||||
rec.misc.seq = i
|
||||
recs.push(rec)
|
||||
|
||||
latestMsgPerFeed.preupdateFromKVT(msg, i)
|
||||
msgsPerFeed.update(rec.msg)
|
||||
},
|
||||
function drainEnd(err) {
|
||||
// prettier-ignore
|
||||
if (err) throw new Error('Failed to initially scan the log', { cause: err });
|
||||
latestMsgPerFeed.commitAllPreupdates()
|
||||
scannedLog.setReady()
|
||||
}
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
function logAppend(key, value, feedId, isOOO, cb) {
|
||||
const kvt = {
|
||||
key,
|
||||
value,
|
||||
timestamp: Date.now(),
|
||||
function logAppend(id, msg, feedId, isOOO, cb) {
|
||||
const rec = {
|
||||
id,
|
||||
msg,
|
||||
received: Date.now(),
|
||||
}
|
||||
if (feedId !== value.author) kvt.feed = feedId
|
||||
if (isOOO) kvt.ooo = isOOO
|
||||
log.append(kvt, (err, newOffset) => {
|
||||
if (isOOO) rec.ooo = isOOO
|
||||
log.append(rec, (err, newOffset) => {
|
||||
if (err) return cb(new Error('logAppend failed', { cause: err }))
|
||||
const offset = newOffset // latestOffset
|
||||
const size = Buffer.from(JSON.stringify(kvt), 'utf8').length
|
||||
const seq = msgs.length
|
||||
const kvtExposed = decrypt(kvt, ssb, config)
|
||||
kvt.meta = kvtExposed.meta = { offset, size, seq }
|
||||
msgs.push(kvtExposed)
|
||||
cb(null, kvt)
|
||||
const size = Buffer.from(JSON.stringify(rec), 'utf8').length
|
||||
const seq = recs.length
|
||||
const recExposed = decrypt(rec, peer, config)
|
||||
rec.misc = recExposed.misc = { offset, size, seq }
|
||||
recs.push(recExposed)
|
||||
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) {
|
||||
if (encryptionFormat.setup) {
|
||||
const loaded = new ReadyGate()
|
||||
|
@ -179,20 +177,6 @@ exports.init = function initMemDB(ssb, config) {
|
|||
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) {
|
||||
if (!ciphertextJS) return null
|
||||
if (typeof ciphertextJS !== 'string') return null
|
||||
|
@ -201,90 +185,57 @@ exports.init = function initMemDB(ssb, config) {
|
|||
return encryptionFormat
|
||||
}
|
||||
|
||||
function add(nativeMsg, cb) {
|
||||
const feedFormat = findFeedFormatForNativeMsg(nativeMsg)
|
||||
if (!feedFormat) {
|
||||
// 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)
|
||||
function add(msg, cb) {
|
||||
const feedId = FeedV1.getFeedId(msg)
|
||||
const existingMsgs = msgsPerFeed.getAll(feedId)
|
||||
|
||||
if (prevNativeMsg) {
|
||||
feedFormat.validate(nativeMsg, prevNativeMsg, hmacKey, validationCB)
|
||||
} else {
|
||||
feedFormat.validateOOO(nativeMsg, hmacKey, validationCB)
|
||||
}
|
||||
FeedV1.validate(msg, existingMsgs, validationCB)
|
||||
|
||||
function validationCB(err) {
|
||||
// prettier-ignore
|
||||
if (err) return cb(new Error('add() failed validation for feed format ' + feedFormat.name, {cause: err}))
|
||||
const msgId = feedFormat.getMsgId(nativeMsg)
|
||||
const msgVal = feedFormat.fromNativeMsg(nativeMsg)
|
||||
latestMsgPerFeed.update(feedId, nativeMsg)
|
||||
if (err) return cb(new Error('add() failed validation for feed format v1', {cause: err}))
|
||||
const msgId = FeedV1.getMsgId(msg)
|
||||
msgsPerFeed.update(msg, msgId)
|
||||
|
||||
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 }))
|
||||
|
||||
onMsgAdded.set({
|
||||
kvt,
|
||||
nativeMsg,
|
||||
feedFormat: feedFormat.name,
|
||||
})
|
||||
cb(null, kvt)
|
||||
})
|
||||
onRecordAdded.set(rec)
|
||||
cb(null, rec)
|
||||
}
|
||||
}
|
||||
|
||||
function create(opts, cb) {
|
||||
const keys = opts.keys ?? config.keys
|
||||
|
||||
const feedFormat = feedFormats.get(opts.feedFormat)
|
||||
const encryptionFormat = encryptionFormats.get(opts.encryptionFormat)
|
||||
// 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 (!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.type) return cb(new Error('create() requires a `type`'))
|
||||
|
||||
// Create full opts:
|
||||
let provisionalNativeMsg
|
||||
let tempMsg
|
||||
try {
|
||||
provisionalNativeMsg = feedFormat.newNativeMsg({
|
||||
timestamp: Date.now(),
|
||||
...opts,
|
||||
previous: null,
|
||||
keys,
|
||||
})
|
||||
tempMsg = FeedV1.create({ when: Date.now(), ...opts, prev: [], keys })
|
||||
} catch (err) {
|
||||
return cb(new Error('create() failed', { cause: err }))
|
||||
}
|
||||
const feedId = feedFormat.getFeedId(provisionalNativeMsg)
|
||||
const previous = latestMsgPerFeed.getAsKV(feedId, feedFormat)
|
||||
const fullOpts = {
|
||||
timestamp: Date.now(),
|
||||
...opts,
|
||||
previous,
|
||||
keys,
|
||||
hmacKey,
|
||||
}
|
||||
const feedId = FeedV1.getFeedId(tempMsg)
|
||||
const prev = msgsPerFeed.getTips(feedId)
|
||||
const fullOpts = { when: Date.now(), ...opts, prev, keys }
|
||||
|
||||
// If opts ask for encryption, encrypt and put ciphertext in opts.content
|
||||
const recps = fullOpts.content.recps
|
||||
if (Array.isArray(recps) && recps.length > 0) {
|
||||
const plaintext = feedFormat.toPlaintextBuffer(fullOpts)
|
||||
const encryptOpts = {
|
||||
...fullOpts,
|
||||
keys,
|
||||
recps,
|
||||
previous: previous ? previous.key : null,
|
||||
}
|
||||
const plaintext = FeedV1.toPlaintextBuffer(fullOpts)
|
||||
const encryptOpts = { ...fullOpts, keys, recps, prev }
|
||||
let ciphertextBuf
|
||||
try {
|
||||
ciphertextBuf = encryptionFormat.encrypt(plaintext, encryptOpts)
|
||||
|
@ -300,80 +251,59 @@ exports.init = function initMemDB(ssb, config) {
|
|||
fullOpts.content = ciphertextBase64 + '.' + encryptionFormat.name
|
||||
}
|
||||
|
||||
// Create the native message:
|
||||
let nativeMsg
|
||||
// Create the actual message:
|
||||
let msg
|
||||
try {
|
||||
nativeMsg = feedFormat.newNativeMsg(fullOpts)
|
||||
msg = FeedV1.create(fullOpts)
|
||||
} catch (err) {
|
||||
return cb(new Error('create() failed', { cause: err }))
|
||||
}
|
||||
const msgId = feedFormat.getMsgId(nativeMsg)
|
||||
const msgVal = feedFormat.fromNativeMsg(nativeMsg, 'js')
|
||||
latestMsgPerFeed.update(feedId, nativeMsg)
|
||||
const msgId = FeedV1.getMsgId(msg)
|
||||
msgsPerFeed.update(msg, msgId)
|
||||
|
||||
// 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
|
||||
if (err) return cb(new Error('create() failed to append the log', { cause: err }))
|
||||
onMsgAdded.set({
|
||||
kvt,
|
||||
nativeMsg,
|
||||
feedFormat: feedFormat.name,
|
||||
})
|
||||
cb(null, kvt)
|
||||
onRecordAdded.set(rec)
|
||||
cb(null, rec)
|
||||
})
|
||||
}
|
||||
|
||||
function del(msgId, cb) {
|
||||
const kvt = getKVT(msgId)
|
||||
latestMsgPerFeed.deleteKVT(kvt)
|
||||
msgs[kvt.meta.seq] = null
|
||||
const rec = getRecord(msgId)
|
||||
msgsPerFeed.deleteMsg(rec.msg)
|
||||
const { offset, size, seq } = rec.misc
|
||||
recs[rec.misc.seq] = { misc: { offset, size, seq } }
|
||||
log.onDrain(() => {
|
||||
log.del(kvt.meta.offset, cb)
|
||||
log.del(offset, cb)
|
||||
})
|
||||
}
|
||||
|
||||
function filterAsPullStream(fn) {
|
||||
let i = 0
|
||||
return function source(end, cb) {
|
||||
if (end) return cb(end)
|
||||
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* msgs() {
|
||||
for (let i = 0; i < recs.length; i++) {
|
||||
const rec = recs[i]
|
||||
if (rec.msg) yield rec.msg
|
||||
}
|
||||
}
|
||||
|
||||
function* filterAsIterator(fn) {
|
||||
for (let i = 0; i < msgs.length; i++) {
|
||||
const msg = msgs[i]
|
||||
if (msg && fn(msg, i, msgs)) yield msg
|
||||
function* records() {
|
||||
for (let i = 0; i < recs.length; i++) {
|
||||
const rec = recs[i]
|
||||
if (rec) yield rec
|
||||
}
|
||||
}
|
||||
|
||||
function filterAsArray(fn) {
|
||||
return msgs.filter(fn)
|
||||
}
|
||||
|
||||
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
|
||||
function getRecord(msgId) {
|
||||
for (let i = 0; i < recs.length; i++) {
|
||||
const rec = recs[i]
|
||||
if (rec && rec.id === msgId) return rec
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function get(msgKey) {
|
||||
return getKVT(msgKey)?.value
|
||||
function get(msgId) {
|
||||
return getRecord(msgId)?.msg
|
||||
}
|
||||
|
||||
function loaded(cb) {
|
||||
|
@ -383,22 +313,18 @@ exports.init = function initMemDB(ssb, config) {
|
|||
|
||||
return {
|
||||
// public
|
||||
installFeedFormat,
|
||||
installEncryptionFormat,
|
||||
loaded,
|
||||
add,
|
||||
create,
|
||||
del,
|
||||
onMsgAdded,
|
||||
filterAsPullStream,
|
||||
filterAsIterator,
|
||||
filterAsArray,
|
||||
forEach,
|
||||
getKVT,
|
||||
onRecordAdded,
|
||||
msgs,
|
||||
records,
|
||||
getRecord,
|
||||
get,
|
||||
|
||||
// internal
|
||||
findEncryptionFormatFor,
|
||||
findFeedFormatForAuthor,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,8 @@
|
|||
"fast-json-stable-stringify": "^2.1.0",
|
||||
"obz": "^1.1.0",
|
||||
"promisify-4loc": "^1.0.0",
|
||||
"push-stream": "^11.2.0"
|
||||
"push-stream": "^11.2.0",
|
||||
"ssb-uri2": "^2.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"c8": "^7.11.0",
|
||||
|
|
109
test/add.test.js
109
test/add.test.js
|
@ -1,112 +1,35 @@
|
|||
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 classic = require('ssb-classic/format')
|
||||
const FeedV1 = require('../lib/feed-v1')
|
||||
const p = require('util').promisify
|
||||
const { generateKeypair } = require('./util')
|
||||
|
||||
const DIR = path.join(os.tmpdir(), 'ppppp-db-add')
|
||||
rimraf.sync(DIR)
|
||||
|
||||
test('add() classic', async (t) => {
|
||||
const ssb = SecretStack({ appKey: caps.shs })
|
||||
test('add()', async (t) => {
|
||||
const keys = generateKeypair('alice')
|
||||
const peer = SecretStack({ appKey: caps.shs })
|
||||
.use(require('../'))
|
||||
.use(require('ssb-classic'))
|
||||
.use(require('ssb-box'))
|
||||
.call(null, {
|
||||
keys: ssbKeys.generate('ed25519', 'alice'),
|
||||
path: DIR,
|
||||
})
|
||||
.call(null, { keys, path: DIR })
|
||||
|
||||
await ssb.db.loaded()
|
||||
await peer.db.loaded()
|
||||
|
||||
const nativeMsg = classic.toNativeMsg(
|
||||
{
|
||||
previous: null,
|
||||
author: '@FCX/tsDLpubCPKKfIrw4gc+SQkHcaD17s7GI6i/ziWY=.ed25519',
|
||||
sequence: 1,
|
||||
timestamp: 1514517067954,
|
||||
hash: 'sha256',
|
||||
content: {
|
||||
const inputMsg = FeedV1.create({
|
||||
keys,
|
||||
when: 1514517067954,
|
||||
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)
|
||||
content: { text: 'This is the first post!' },
|
||||
prev: [],
|
||||
})
|
||||
|
||||
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)
|
||||
const rec = await p(peer.db.add)(inputMsg)
|
||||
t.equal(rec.msg.content.text, 'This is the first post!')
|
||||
|
||||
await p(peer.close)(true)
|
||||
})
|
||||
|
|
|
@ -1,57 +1,92 @@
|
|||
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 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)
|
||||
|
||||
let ssb
|
||||
const keys = generateKeypair('alice')
|
||||
let peer
|
||||
test('setup', async (t) => {
|
||||
ssb = SecretStack({ appKey: caps.shs })
|
||||
peer = SecretStack({ appKey: caps.shs })
|
||||
.use(require('../'))
|
||||
.use(require('ssb-classic'))
|
||||
.use(require('ssb-box'))
|
||||
.call(null, {
|
||||
keys: ssbKeys.generate('ed25519', 'alice'),
|
||||
path: DIR,
|
||||
.call(null, { keys, path: DIR })
|
||||
|
||||
await peer.db.loaded()
|
||||
})
|
||||
|
||||
await ssb.db.loaded()
|
||||
let msgHash1
|
||||
let rec1
|
||||
let msgHash2
|
||||
test('create()', async (t) => {
|
||||
rec1 = await p(peer.db.create)({
|
||||
type: 'post',
|
||||
content: { text: 'I am 1st post' },
|
||||
})
|
||||
t.equal(rec1.msg.content.text, 'I am 1st post', 'msg1 text correct')
|
||||
msgHash1 = FeedV1.getMsgHash(rec1.msg)
|
||||
|
||||
const rec2 = await p(peer.db.create)({
|
||||
type: 'post',
|
||||
content: { text: 'I am 2nd post' },
|
||||
})
|
||||
t.equal(rec2.msg.content.text, 'I am 2nd post', 'msg2 text correct')
|
||||
t.deepEquals(rec2.msg.metadata.prev, [msgHash1], 'msg2 prev correct')
|
||||
msgHash2 = FeedV1.getMsgHash(rec2.msg)
|
||||
})
|
||||
|
||||
test('create() classic', async (t) => {
|
||||
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')
|
||||
|
||||
const msg2 = await p(ssb.db.create)({
|
||||
content: { type: 'post', text: 'I am hungry 2' },
|
||||
feedFormat: 'classic',
|
||||
})
|
||||
t.equal(msg2.value.content.text, 'I am hungry 2', 'msg2 text correct')
|
||||
t.equal(msg2.value.previous, msg1.key, 'msg2 previous correct')
|
||||
test('add() forked then create() merged', async (t) => {
|
||||
const msg3 = FeedV1.create({
|
||||
keys,
|
||||
when: Date.now(),
|
||||
type: 'post',
|
||||
content: { text: '3rd post forked from 1st' },
|
||||
prev: [rec1.msg],
|
||||
})
|
||||
|
||||
test('create() classic box', async (t) => {
|
||||
const msgBoxed = await p(ssb.db.create)({
|
||||
feedFormat: 'classic',
|
||||
content: { type: 'post', text: 'I am chewing food', recps: [ssb.id] },
|
||||
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',
|
||||
})
|
||||
t.equal(typeof msgBoxed.value.content, 'string')
|
||||
t.true(msgBoxed.value.content.endsWith('.box'), '.box')
|
||||
t.equal(typeof recEncrypted.msg.content, 'string')
|
||||
t.true(recEncrypted.msg.content.endsWith('.box'), '.box')
|
||||
|
||||
const msgVal = ssb.db.get(msgBoxed.key)
|
||||
t.equals(msgVal.content.text, 'I am chewing food')
|
||||
const msgDecrypted = peer.db.get(recEncrypted.id)
|
||||
t.equals(msgDecrypted.content.text, 'I am chewing food')
|
||||
})
|
||||
|
||||
test('teardown', (t) => {
|
||||
ssb.close(t.end)
|
||||
peer.close(t.end)
|
||||
})
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
const test = require('tape')
|
||||
const ssbKeys = require('ssb-keys')
|
||||
const path = require('path')
|
||||
const os = require('os')
|
||||
const rimraf = require('rimraf')
|
||||
|
@ -8,47 +7,47 @@ const AAOL = require('async-append-only-log')
|
|||
const push = require('push-stream')
|
||||
const caps = require('ssb-caps')
|
||||
const p = require('util').promisify
|
||||
const { generateKeypair } = require('./util')
|
||||
|
||||
const DIR = path.join(os.tmpdir(), 'ppppp-db-del')
|
||||
rimraf.sync(DIR)
|
||||
|
||||
test('del', async (t) => {
|
||||
const ssb = SecretStack({ appKey: caps.shs })
|
||||
const keys = generateKeypair('alice')
|
||||
const peer = SecretStack({ appKey: caps.shs })
|
||||
.use(require('../'))
|
||||
.use(require('ssb-classic'))
|
||||
.call(null, {
|
||||
keys: ssbKeys.generate('ed25519', 'alice'),
|
||||
path: DIR,
|
||||
})
|
||||
.call(null, { keys, path: DIR })
|
||||
|
||||
await ssb.db.loaded()
|
||||
await peer.db.loaded()
|
||||
|
||||
const msgIDs = []
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const msg = await p(ssb.db.create)({
|
||||
feedFormat: 'classic',
|
||||
content: { type: 'post', text: 'm' + i },
|
||||
const rec = await p(peer.db.create)({
|
||||
type: 'post',
|
||||
content: { text: 'm' + i },
|
||||
})
|
||||
msgIDs.push(msg.key)
|
||||
msgIDs.push(rec.id)
|
||||
}
|
||||
|
||||
const before = ssb.db
|
||||
.filterAsArray(() => true)
|
||||
.map((msg) => msg.value.content.text)
|
||||
const before = []
|
||||
for (const msg of peer.db.msgs()) {
|
||||
before.push(msg.content.text)
|
||||
}
|
||||
|
||||
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
|
||||
.filterAsArray(() => true)
|
||||
.map((msg) => msg?.value.content.text ?? null)
|
||||
const after = []
|
||||
for (const msg of peer.db.msgs()) {
|
||||
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,
|
||||
blockSize: 64 * 1024,
|
||||
codec: {
|
||||
|
@ -66,10 +65,8 @@ test('del', async (t) => {
|
|||
log.stream({ offsets: true, values: true, sizes: true }).pipe(
|
||||
push.drain(
|
||||
function drainEach({ offset, value, size }) {
|
||||
if (!value) {
|
||||
persistedMsgs.push(null)
|
||||
} else {
|
||||
persistedMsgs.push(value)
|
||||
if (value) {
|
||||
persistedMsgs.push(value.msg)
|
||||
}
|
||||
},
|
||||
function drainEnd(err) {
|
||||
|
@ -81,8 +78,8 @@ test('del', async (t) => {
|
|||
})
|
||||
|
||||
t.deepEqual(
|
||||
persistedMsgs.map((msg) => msg?.value.content.text ?? null),
|
||||
['m0', 'm1', null, 'm3', 'm4'],
|
||||
persistedMsgs.map((msg) => msg.content.text),
|
||||
['m0', 'm1', 'm3', 'm4'],
|
||||
'msgs in disk after the delete'
|
||||
)
|
||||
})
|
||||
|
|
|
@ -1,91 +1,82 @@
|
|||
const tape = require('tape')
|
||||
const dagfeed = require('../lib/feed-v1')
|
||||
const FeedV1 = require('../lib/feed-v1')
|
||||
const { generateKeypair } = require('./util')
|
||||
|
||||
tape('encode/decode works', (t) => {
|
||||
const keys = generateKeypair('alice')
|
||||
const hmacKey = null
|
||||
const content = { text: 'Hello world!' }
|
||||
const timestamp = 1652037377204
|
||||
const when = 1652037377204
|
||||
|
||||
const nmsg1 = dagfeed.newNativeMsg({
|
||||
const msg1 = FeedV1.create({
|
||||
keys,
|
||||
content,
|
||||
type: 'post',
|
||||
previous: [],
|
||||
timestamp,
|
||||
hmacKey,
|
||||
prev: [],
|
||||
when,
|
||||
})
|
||||
t.deepEquals(
|
||||
Object.keys(msg1.metadata),
|
||||
['depth', 'prev', 'proof', 'size', 'type', 'who', 'when'],
|
||||
'metadata fields'
|
||||
)
|
||||
t.equals(
|
||||
nmsg1.metadata.author,
|
||||
msg1.metadata.who,
|
||||
'4mjQ5aJu378cEu6TksRG3uXAiKFiwGjYQtWAjfVjDAJW',
|
||||
'metadata.author is correct'
|
||||
'metadata.who'
|
||||
)
|
||||
t.equals(nmsg1.metadata.type, 'post', 'metadata.type is correct')
|
||||
t.deepEquals(nmsg1.metadata.previous, [], 'metadata.previous is correct')
|
||||
console.log(nmsg1)
|
||||
t.equals(msg1.metadata.type, 'post', 'metadata.type')
|
||||
t.equals(msg1.metadata.depth, 0, 'metadata.depth')
|
||||
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 = {
|
||||
key: dagfeed.getMsgId(nmsg1),
|
||||
value: dagfeed.fromNativeMsg(nmsg1),
|
||||
timestamp: Date.now(),
|
||||
}
|
||||
console.log(msg1)
|
||||
|
||||
const msgHash1 = 'HEzse89DSDWUXVPyav35GC'
|
||||
const msgKey1 =
|
||||
'ssb:message/dag/4mjQ5aJu378cEu6TksRG3uXAiKFiwGjYQtWAjfVjDAJW/post/' +
|
||||
msgHash1
|
||||
const msgHash = '9cYegpVpddoMSdvSf53dTH'
|
||||
|
||||
t.deepEqual(jsonMsg.key, msgKey1, 'key is correct')
|
||||
t.deepEqual(
|
||||
jsonMsg.value.author,
|
||||
'ssb:feed/dag/4mjQ5aJu378cEu6TksRG3uXAiKFiwGjYQtWAjfVjDAJW/post',
|
||||
'author is correct'
|
||||
t.equals(
|
||||
FeedV1.getMsgId(msg1),
|
||||
'ppppp:message/v1/4mjQ5aJu378cEu6TksRG3uXAiKFiwGjYQtWAjfVjDAJW/post/' +
|
||||
msgHash,
|
||||
'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)
|
||||
t.deepEqual(reconstructedNMsg1, nmsg1, 'can reconstruct')
|
||||
const content2 = { text: 'Ola mundo!' }
|
||||
|
||||
const content2 = { text: 'Hello butty world!' }
|
||||
|
||||
const nmsg2 = dagfeed.newNativeMsg({
|
||||
const msg2 = FeedV1.create({
|
||||
keys,
|
||||
content: content2,
|
||||
type: 'post',
|
||||
previous: [msgHash1],
|
||||
timestamp: timestamp + 1,
|
||||
hmacKey,
|
||||
prev: [msg1],
|
||||
when: when + 1,
|
||||
})
|
||||
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 = {
|
||||
key: dagfeed.getMsgId(nmsg2),
|
||||
value: dagfeed.fromNativeMsg(nmsg2),
|
||||
timestamp: Date.now(),
|
||||
}
|
||||
console.log(msg2)
|
||||
|
||||
t.deepEqual(
|
||||
jsonMsg2.key,
|
||||
'ssb:message/dag/4mjQ5aJu378cEu6TksRG3uXAiKFiwGjYQtWAjfVjDAJW/post/U5n4v1m7gFzrtrdK84gGsV',
|
||||
'key is correct'
|
||||
FeedV1.getMsgId(msg2),
|
||||
'ppppp:message/v1/4mjQ5aJu378cEu6TksRG3uXAiKFiwGjYQtWAjfVjDAJW/post/LEH1JVENvJgSpBBrVUwJx6',
|
||||
'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()
|
||||
})
|
||||
|
|
|
@ -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 dagfeed = require('../lib/feed-v1')
|
||||
const FeedV1 = require('../lib/feed-v1')
|
||||
const { generateKeypair } = require('./util')
|
||||
|
||||
tape('invalid type not a string', function (t) {
|
||||
tape('invalid type not a string', (t) => {
|
||||
const keys = generateKeypair('alice')
|
||||
const hmacKey = null
|
||||
|
||||
t.throws(
|
||||
() => {
|
||||
dagfeed.newNativeMsg({
|
||||
FeedV1.create({
|
||||
keys,
|
||||
content: { text: 'Hello world!' },
|
||||
timestamp: 1652037377204,
|
||||
when: 1652037377204,
|
||||
type: 123,
|
||||
previous: [],
|
||||
hmacKey,
|
||||
prev: [],
|
||||
})
|
||||
},
|
||||
/type is not a string/,
|
||||
|
@ -23,19 +21,17 @@ tape('invalid type not a string', function (t) {
|
|||
t.end()
|
||||
})
|
||||
|
||||
tape('invalid type with "/" character', function (t) {
|
||||
tape('invalid type with "/" character', (t) => {
|
||||
const keys = generateKeypair('alice')
|
||||
const hmacKey = null
|
||||
|
||||
t.throws(
|
||||
() => {
|
||||
dagfeed.newNativeMsg({
|
||||
FeedV1.create({
|
||||
keys,
|
||||
content: { text: 'Hello world!' },
|
||||
timestamp: 1652037377204,
|
||||
when: 1652037377204,
|
||||
type: 'group/init',
|
||||
previous: [],
|
||||
hmacKey,
|
||||
prev: [],
|
||||
})
|
||||
},
|
||||
/invalid type/,
|
||||
|
@ -44,19 +40,17 @@ tape('invalid type with "/" character', function (t) {
|
|||
t.end()
|
||||
})
|
||||
|
||||
tape('invalid type with "*" character', function (t) {
|
||||
tape('invalid type with "*" character', (t) => {
|
||||
const keys = generateKeypair('alice')
|
||||
const hmacKey = null
|
||||
|
||||
t.throws(
|
||||
() => {
|
||||
dagfeed.newNativeMsg({
|
||||
FeedV1.create({
|
||||
keys,
|
||||
content: { text: 'Hello world!' },
|
||||
timestamp: 1652037377204,
|
||||
when: 1652037377204,
|
||||
type: 'star*',
|
||||
previous: [],
|
||||
hmacKey,
|
||||
prev: [],
|
||||
})
|
||||
},
|
||||
/invalid type/,
|
||||
|
@ -65,19 +59,17 @@ tape('invalid type with "*" character', function (t) {
|
|||
t.end()
|
||||
})
|
||||
|
||||
tape('invalid type too short', function (t) {
|
||||
tape('invalid type too short', (t) => {
|
||||
const keys = generateKeypair('alice')
|
||||
const hmacKey = null
|
||||
|
||||
t.throws(
|
||||
() => {
|
||||
dagfeed.newNativeMsg({
|
||||
FeedV1.create({
|
||||
keys,
|
||||
content: { text: 'Hello world!' },
|
||||
timestamp: 1652037377204,
|
||||
when: 1652037377204,
|
||||
type: 'xy',
|
||||
previous: [],
|
||||
hmacKey,
|
||||
prev: [],
|
||||
})
|
||||
},
|
||||
/shorter than 3/,
|
||||
|
@ -86,19 +78,17 @@ tape('invalid type too short', function (t) {
|
|||
t.end()
|
||||
})
|
||||
|
||||
tape('invalid type too long', function (t) {
|
||||
tape('invalid type too long', (t) => {
|
||||
const keys = generateKeypair('alice')
|
||||
const hmacKey = null
|
||||
|
||||
t.throws(
|
||||
() => {
|
||||
dagfeed.newNativeMsg({
|
||||
FeedV1.create({
|
||||
keys,
|
||||
content: { text: 'Hello world!' },
|
||||
timestamp: 1652037377204,
|
||||
when: 1652037377204,
|
||||
type: 'a'.repeat(120),
|
||||
previous: [],
|
||||
hmacKey,
|
||||
prev: [],
|
||||
})
|
||||
},
|
||||
/100\+ characters long/,
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
const tape = require('tape')
|
||||
const base58 = require('bs58')
|
||||
const dagfeed = require('../lib/feed-v1')
|
||||
const FeedV1 = require('../lib/feed-v1')
|
||||
const { generateKeypair } = require('./util')
|
||||
|
||||
tape('validate 1st msg', (t) => {
|
||||
const keys = generateKeypair('alice')
|
||||
const hmacKey = null
|
||||
|
||||
const nmsg1 = dagfeed.newNativeMsg({
|
||||
const msg = FeedV1.create({
|
||||
keys,
|
||||
content: { text: 'Hello world!' },
|
||||
type: 'post',
|
||||
previous: [],
|
||||
timestamp: 1652030001000,
|
||||
hmacKey,
|
||||
prev: [],
|
||||
when: 1652030001000,
|
||||
})
|
||||
|
||||
dagfeed.validate(nmsg1, null, null, (err) => {
|
||||
FeedV1.validate(msg, [], (err) => {
|
||||
if (err) console.log(err)
|
||||
t.error(err, 'valid 1st msg')
|
||||
t.end()
|
||||
|
@ -25,28 +23,27 @@ tape('validate 1st msg', (t) => {
|
|||
|
||||
tape('validate 2nd msg with existing nativeMsg', (t) => {
|
||||
const keys = generateKeypair('alice')
|
||||
const hmacKey = null
|
||||
|
||||
const nmsg1 = dagfeed.newNativeMsg({
|
||||
const msg1 = FeedV1.create({
|
||||
keys,
|
||||
content: { text: 'Hello world!' },
|
||||
type: 'post',
|
||||
previous: [],
|
||||
timestamp: 1652030001000,
|
||||
hmacKey,
|
||||
prev: [],
|
||||
when: 1652030001000,
|
||||
})
|
||||
const msgKey1 = dagfeed.getMsgId(nmsg1)
|
||||
const msgHash1 = FeedV1.getMsgHash(msg1)
|
||||
|
||||
const nmsg2 = dagfeed.newNativeMsg({
|
||||
const msg2 = FeedV1.create({
|
||||
keys,
|
||||
content: { text: 'Hello world!' },
|
||||
type: 'post',
|
||||
previous: [msgKey1],
|
||||
timestamp: 1652030002000,
|
||||
hmacKey,
|
||||
prev: [msg1],
|
||||
when: 1652030002000,
|
||||
})
|
||||
|
||||
dagfeed.validate(nmsg2, [nmsg1], null, (err) => {
|
||||
const existing = new Map()
|
||||
existing.set(msgHash1, msg1)
|
||||
FeedV1.validate(msg2, existing, (err) => {
|
||||
if (err) console.log(err)
|
||||
t.error(err, 'valid 2nd msg')
|
||||
t.end()
|
||||
|
@ -55,60 +52,28 @@ tape('validate 2nd msg with existing nativeMsg', (t) => {
|
|||
|
||||
tape('validate 2nd msg with existing msgId', (t) => {
|
||||
const keys = generateKeypair('alice')
|
||||
const hmacKey = null
|
||||
|
||||
const nmsg1 = dagfeed.newNativeMsg({
|
||||
const msg1 = FeedV1.create({
|
||||
keys,
|
||||
content: { text: 'Hello world!' },
|
||||
type: 'post',
|
||||
previous: [],
|
||||
timestamp: 1652030001000,
|
||||
hmacKey,
|
||||
prev: [],
|
||||
when: 1652030001000,
|
||||
})
|
||||
const msgKey1 = dagfeed.getMsgId(nmsg1)
|
||||
const msgKey1 = FeedV1.getMsgId(msg1)
|
||||
const msgHash1 = FeedV1.getMsgHash(msg1)
|
||||
|
||||
const nmsg2 = dagfeed.newNativeMsg({
|
||||
const msg2 = FeedV1.create({
|
||||
keys,
|
||||
content: { text: 'Hello world!' },
|
||||
type: 'post',
|
||||
previous: [msgKey1],
|
||||
timestamp: 1652030002000,
|
||||
hmacKey,
|
||||
prev: [msg1],
|
||||
when: 1652030002000,
|
||||
})
|
||||
|
||||
dagfeed.validate(nmsg2, [msgKey1], null, (err) => {
|
||||
if (err) console.log(err)
|
||||
t.error(err, 'valid 2nd msg')
|
||||
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) => {
|
||||
const existing = new Map()
|
||||
existing.set(msgHash1, msg1)
|
||||
FeedV1.validate(msg2, existing, (err) => {
|
||||
if (err) console.log(err)
|
||||
t.error(err, 'valid 2nd msg')
|
||||
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) => {
|
||||
const keys = generateKeypair('alice')
|
||||
const hmacKey = null
|
||||
|
||||
const nmsg1 = dagfeed.newNativeMsg({
|
||||
const msg1 = FeedV1.create({
|
||||
keys,
|
||||
content: { text: 'Hello world!' },
|
||||
type: 'post',
|
||||
previous: [],
|
||||
timestamp: 1652030001000,
|
||||
hmacKey,
|
||||
prev: [],
|
||||
when: 1652030001000,
|
||||
})
|
||||
const kvt1 = {
|
||||
key: dagfeed.getMsgId(nmsg1),
|
||||
value: dagfeed.fromNativeMsg(nmsg1),
|
||||
timestamp: Date.now(),
|
||||
}
|
||||
const msgHash1 = FeedV1.getMsgHash(msg1)
|
||||
|
||||
const nmsg2 = dagfeed.newNativeMsg({
|
||||
const msg2 = FeedV1.create({
|
||||
keys,
|
||||
content: { text: 'Hello world!' },
|
||||
type: 'post',
|
||||
previous: [kvt1.key],
|
||||
timestamp: 1652030002000,
|
||||
hmacKey,
|
||||
prev: [msg1],
|
||||
when: 1652030002000,
|
||||
})
|
||||
|
||||
dagfeed.validate(nmsg2, [kvt1], null, (err) => {
|
||||
const existing = new Map()
|
||||
existing.set(msgHash1, msg1)
|
||||
FeedV1.validate(msg2,existing, (err) => {
|
||||
if (err) console.log(err)
|
||||
t.error(err, 'valid 2nd msg')
|
||||
t.end()
|
||||
|
@ -151,37 +111,37 @@ tape('validate 2nd msg with existing KVT', (t) => {
|
|||
|
||||
tape('validate 2nd forked msg', (t) => {
|
||||
const keys = generateKeypair('alice')
|
||||
const hmacKey = null
|
||||
|
||||
const nmsg1 = dagfeed.newNativeMsg({
|
||||
const msg1 = FeedV1.create({
|
||||
keys,
|
||||
content: { text: 'Hello world!' },
|
||||
type: 'post',
|
||||
previous: [],
|
||||
timestamp: 1652030001000,
|
||||
hmacKey,
|
||||
prev: [],
|
||||
when: 1652030001000,
|
||||
})
|
||||
const msgKey1 = dagfeed.getMsgId(nmsg1)
|
||||
const msgHash1 = FeedV1.getMsgHash(msg1)
|
||||
|
||||
const nmsg2A = dagfeed.newNativeMsg({
|
||||
const msg2A = FeedV1.create({
|
||||
keys,
|
||||
content: { text: 'Hello world!' },
|
||||
type: 'post',
|
||||
previous: [msgKey1],
|
||||
timestamp: 1652030002000,
|
||||
hmacKey,
|
||||
prev: [msg1],
|
||||
when: 1652030002000,
|
||||
})
|
||||
const msgHash2A = FeedV1.getMsgHash(msg2A)
|
||||
|
||||
const nmsg2B = dagfeed.newNativeMsg({
|
||||
const msg2B = FeedV1.create({
|
||||
keys,
|
||||
content: { text: 'Hello world!' },
|
||||
type: 'post',
|
||||
previous: [msgKey1],
|
||||
timestamp: 1652030003000,
|
||||
hmacKey,
|
||||
prev: [msg1],
|
||||
when: 1652030003000,
|
||||
})
|
||||
|
||||
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)
|
||||
t.error(err, 'valid 2nd forked msg')
|
||||
t.end()
|
||||
|
@ -190,33 +150,34 @@ tape('validate 2nd forked msg', (t) => {
|
|||
|
||||
tape('invalid msg with unknown previous', (t) => {
|
||||
const keys = generateKeypair('alice')
|
||||
const hmacKey = null
|
||||
|
||||
const nmsg1 = dagfeed.newNativeMsg({
|
||||
const msg1 = FeedV1.create({
|
||||
keys,
|
||||
content: { text: 'Hello world!' },
|
||||
type: 'post',
|
||||
previous: [],
|
||||
timestamp: 1652030001000,
|
||||
hmacKey,
|
||||
prev: [],
|
||||
when: 1652030001000,
|
||||
})
|
||||
const msgHash1 = FeedV1.getMsgHash(msg1)
|
||||
|
||||
const fakeMsgKey1 = base58.encode(Buffer.alloc(16).fill(42))
|
||||
|
||||
const nmsg2 = dagfeed.newNativeMsg({
|
||||
const msg2 = FeedV1.create({
|
||||
keys,
|
||||
content: { text: 'Hello world!' },
|
||||
type: 'post',
|
||||
previous: [fakeMsgKey1],
|
||||
timestamp: 1652030002000,
|
||||
hmacKey,
|
||||
prev: [msg1],
|
||||
when: 1652030002000,
|
||||
})
|
||||
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.match(
|
||||
err.message,
|
||||
/previous .+ is not a known message ID/,
|
||||
/prev .+ is not locally known/,
|
||||
'invalid 2nd msg description'
|
||||
)
|
||||
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) {
|
||||
const keys = ssbKeys.generate('ed25519', seed, 'buttwoo-v1')
|
||||
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
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue