mirror of https://codeberg.org/pzp/pzp-db.git
use TypeScript in JSDoc
This commit is contained in:
parent
aab707f5da
commit
674e2ba66c
|
@ -3,6 +3,7 @@ node_modules
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
package-lock.json
|
package-lock.json
|
||||||
coverage
|
coverage
|
||||||
|
**/*.d.ts
|
||||||
*~
|
*~
|
||||||
|
|
||||||
# For misc scripts and experiments:
|
# For misc scripts and experiments:
|
||||||
|
|
|
@ -3,10 +3,23 @@ const b4a = require('b4a')
|
||||||
const MsgV3 = require('./msg-v3')
|
const MsgV3 = require('./msg-v3')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./index').Rec} Rec
|
* @typedef {import('./index').RecPresent} RecPresent
|
||||||
* @typedef {import('ppppp-keypair').Keypair} Keypair
|
* @typedef {import('ppppp-keypair').Keypair} Keypair
|
||||||
|
*
|
||||||
|
* @typedef {Buffer | Uint8Array} B4A
|
||||||
|
*
|
||||||
|
* @typedef {{
|
||||||
|
* name: string;
|
||||||
|
* setup?: (config: any, cb: any) => void;
|
||||||
|
* onReady?: (cb: any) => void;
|
||||||
|
* encrypt: (plaintext: B4A, opts: any) => B4A;
|
||||||
|
* decrypt: (ciphertext: B4A, opts: any) => B4A | null;
|
||||||
|
* }} EncryptionFormat
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} str
|
||||||
|
*/
|
||||||
function ciphertextStrToBuffer(str) {
|
function ciphertextStrToBuffer(str) {
|
||||||
const dot = str.indexOf('.')
|
const dot = str.indexOf('.')
|
||||||
return b4a.from(str.slice(0, dot), 'base64')
|
return b4a.from(str.slice(0, dot), 'base64')
|
||||||
|
@ -17,21 +30,21 @@ function ciphertextStrToBuffer(str) {
|
||||||
* @param {Keypair} keypair
|
* @param {Keypair} keypair
|
||||||
*/
|
*/
|
||||||
function keypairToSSBKeys(keypair) {
|
function keypairToSSBKeys(keypair) {
|
||||||
const public = b4a.from(base58.decode(keypair.public)).toString('base64')
|
const _public = b4a.from(base58.decode(keypair.public)).toString('base64')
|
||||||
const private = b4a.from(base58.decode(keypair.private)).toString('base64')
|
const _private = b4a.from(base58.decode(keypair.private)).toString('base64')
|
||||||
return {
|
return {
|
||||||
id: `@${public}.ed25519`,
|
id: `@${_public}.ed25519`,
|
||||||
curve: keypair.curve,
|
curve: keypair.curve,
|
||||||
public,
|
public: _public,
|
||||||
private,
|
private: _private,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Rec} rec
|
* @param {RecPresent} rec
|
||||||
* @param {any} peer
|
* @param {any} peer
|
||||||
* @param {any} config
|
* @param {any} config
|
||||||
* @returns {Rec}
|
* @returns {RecPresent}
|
||||||
*/
|
*/
|
||||||
function decrypt(rec, peer, config) {
|
function decrypt(rec, peer, config) {
|
||||||
const msgEncrypted = rec.msg
|
const msgEncrypted = rec.msg
|
||||||
|
@ -63,6 +76,9 @@ function decrypt(rec, peer, config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {RecPresent} rec
|
||||||
|
*/
|
||||||
function reEncrypt(rec) {
|
function reEncrypt(rec) {
|
||||||
return {
|
return {
|
||||||
hash: rec.hash,
|
hash: rec.hash,
|
||||||
|
|
224
lib/index.js
224
lib/index.js
|
@ -1,47 +1,63 @@
|
||||||
const path = require('node:path')
|
const path = require('node:path')
|
||||||
|
// @ts-ignore
|
||||||
const push = require('push-stream')
|
const push = require('push-stream')
|
||||||
|
// @ts-ignore
|
||||||
const AAOL = require('async-append-only-log')
|
const AAOL = require('async-append-only-log')
|
||||||
const promisify = require('promisify-4loc')
|
const promisify = require('promisify-4loc')
|
||||||
const b4a = require('b4a')
|
const b4a = require('b4a')
|
||||||
const base58 = require('bs58')
|
const base58 = require('bs58')
|
||||||
|
// @ts-ignore
|
||||||
const Obz = require('obz')
|
const Obz = require('obz')
|
||||||
const MsgV2 = require('./msg-v3')
|
const MsgV2 = require('./msg-v3')
|
||||||
const { ReadyGate } = require('./utils')
|
const { ReadyGate } = require('./utils')
|
||||||
const { decrypt } = require('./encryption')
|
const { decrypt } = require('./encryption')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @typedef {import('ppppp-keypair').Keypair} Keypair
|
||||||
* @typedef {import('./msg-v3').Msg} Msg
|
* @typedef {import('./msg-v3').Msg} Msg
|
||||||
|
* @typedef {import('./encryption').EncryptionFormat} EncryptionFormat
|
||||||
|
*
|
||||||
|
* @typedef {Buffer | Uint8Array} B4A
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} RecDeleted
|
* @typedef {{
|
||||||
* @property {never} hash
|
* hash?: never;
|
||||||
* @property {never} msg
|
* msg?: never;
|
||||||
* @property {never} received
|
* received?: never;
|
||||||
* @property {Object} misc
|
* misc: {
|
||||||
* @property {number} misc.offset
|
* offset: number;
|
||||||
* @property {number} misc.size
|
* size: number;
|
||||||
* @property {number} misc.seq
|
* seq: number;
|
||||||
*/
|
* };
|
||||||
|
* }} RecDeleted
|
||||||
/**
|
*
|
||||||
* @typedef {Object} RecPresent
|
* @typedef {{
|
||||||
* @property {string} hash
|
* hash: string;
|
||||||
* @property {Msg} msg
|
* msg: Msg;
|
||||||
* @property {number} received
|
* received: number;
|
||||||
* @property {Object} misc
|
* misc: {
|
||||||
* @property {number} misc.offset
|
* offset: number;
|
||||||
* @property {number} misc.size
|
* size: number;
|
||||||
* @property {number} misc.seq
|
* seq: number;
|
||||||
* @property {boolean=} misc.private
|
* private?: boolean;
|
||||||
* @property {Object=} misc.originalData
|
* originalData?: any;
|
||||||
* @property {string=} misc.encryptionFormat
|
* encryptionFormat?: string;
|
||||||
*/
|
* }
|
||||||
|
* }} RecPresent
|
||||||
/**
|
*
|
||||||
* @typedef {RecPresent | RecDeleted} Rec
|
* @typedef {RecPresent | RecDeleted} Rec
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @typedef {(...args: [Error] | [null, T]) => void} CB
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {(...args: [Error] | []) => void} CBVoid
|
||||||
|
*/
|
||||||
|
|
||||||
class DBTangle extends MsgV2.Tangle {
|
class DBTangle extends MsgV2.Tangle {
|
||||||
/**
|
/**
|
||||||
* @param {string} rootHash
|
* @param {string} rootHash
|
||||||
|
@ -55,6 +71,9 @@ class DBTangle extends MsgV2.Tangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} msgHash
|
||||||
|
*/
|
||||||
getDeletablesAndErasables(msgHash) {
|
getDeletablesAndErasables(msgHash) {
|
||||||
const erasables = this.shortestPathToRoot(msgHash)
|
const erasables = this.shortestPathToRoot(msgHash)
|
||||||
const sorted = this.topoSort()
|
const sorted = this.topoSort()
|
||||||
|
@ -66,28 +85,42 @@ class DBTangle extends MsgV2.Tangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.name = 'db'
|
/**
|
||||||
|
* @param {any} peer
|
||||||
exports.init = function initDB(peer, config) {
|
* @param {{ path: string; keypair: Keypair; }} config
|
||||||
|
*/
|
||||||
|
function initDB(peer, config) {
|
||||||
/** @type {Array<Rec>} */
|
/** @type {Array<Rec>} */
|
||||||
const recs = []
|
const recs = []
|
||||||
|
|
||||||
|
/** @type {Map<string, EncryptionFormat>} */
|
||||||
const encryptionFormats = new Map()
|
const encryptionFormats = new Map()
|
||||||
|
|
||||||
const onRecordAdded = Obz()
|
const onRecordAdded = Obz()
|
||||||
|
|
||||||
const log = AAOL(path.join(config.path, 'db.bin'), {
|
const log = AAOL(path.join(config.path, 'db.bin'), {
|
||||||
cacheSize: 1,
|
cacheSize: 1,
|
||||||
blockSize: 64 * 1024,
|
blockSize: 64 * 1024,
|
||||||
codec: {
|
codec: {
|
||||||
|
/**
|
||||||
|
* @param {Msg} msg
|
||||||
|
*/
|
||||||
encode(msg) {
|
encode(msg) {
|
||||||
return b4a.from(JSON.stringify(msg), 'utf8')
|
return b4a.from(JSON.stringify(msg), 'utf8')
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* @param {B4A} buf
|
||||||
|
*/
|
||||||
decode(buf) {
|
decode(buf) {
|
||||||
return JSON.parse(buf.toString('utf8'))
|
return JSON.parse(b4a.toString(buf, 'utf8'))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* @param {B4A} buf
|
||||||
|
*/
|
||||||
validateRecord(buf) {
|
validateRecord(buf) {
|
||||||
try {
|
try {
|
||||||
JSON.parse(buf.toString('utf8'))
|
JSON.parse(b4a.toString(buf, 'utf8'))
|
||||||
return true
|
return true
|
||||||
} catch {
|
} catch {
|
||||||
return false
|
return false
|
||||||
|
@ -95,8 +128,9 @@ exports.init = function initDB(peer, config) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
peer.close.hook(function (fn, args) {
|
peer.close.hook(function (/** @type {any} */ fn, /** @type {any} */ args) {
|
||||||
log.close(() => {
|
log.close(() => {
|
||||||
|
// @ts-ignore
|
||||||
fn.apply(this, args)
|
fn.apply(this, args)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -107,24 +141,27 @@ exports.init = function initDB(peer, config) {
|
||||||
let i = -1
|
let i = -1
|
||||||
log.stream({ offsets: true, values: true, sizes: true }).pipe(
|
log.stream({ offsets: true, values: true, sizes: true }).pipe(
|
||||||
push.drain(
|
push.drain(
|
||||||
|
// @ts-ignore
|
||||||
function drainEach({ offset, value, size }) {
|
function drainEach({ offset, value, size }) {
|
||||||
i += 1
|
i += 1
|
||||||
if (!value) {
|
if (!value) {
|
||||||
// deleted record
|
// deleted record
|
||||||
recs.push({ misc: { offset, size, seq: i } })
|
/** @type {RecDeleted} */
|
||||||
|
const rec = { misc: { offset, size, seq: i } }
|
||||||
|
recs.push(rec)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// TODO: for performance, dont decrypt on startup, instead decrypt on
|
// TODO: for performance, dont decrypt on startup, instead decrypt on
|
||||||
// demand, or decrypt in the background. Or then store the log with
|
// demand, or decrypt in the background. Or then store the log with
|
||||||
// decrypted msgs and only encrypt when moving it to the network.
|
// decrypted msgs and only encrypt when moving it to the network.
|
||||||
const rec = decrypt(value, peer, config)
|
const rec = decrypt(value, peer, config)
|
||||||
rec.misc ??= {}
|
rec.misc ??= /** @type {Rec['misc']} */ ({})
|
||||||
rec.misc.offset = offset
|
rec.misc.offset = offset
|
||||||
rec.misc.size = size
|
rec.misc.size = size
|
||||||
rec.misc.seq = i
|
rec.misc.seq = i
|
||||||
recs.push(rec)
|
recs.push(rec)
|
||||||
},
|
},
|
||||||
function drainEnd(err) {
|
function drainEnd(/** @type {any} */ err) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
if (err) throw new Error('Failed to initially scan the log', { cause: err });
|
if (err) throw new Error('Failed to initially scan the log', { cause: err });
|
||||||
scannedLog.setReady()
|
scannedLog.setReady()
|
||||||
|
@ -133,13 +170,26 @@ exports.init = function initDB(peer, config) {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} hash
|
||||||
|
* @param {Msg} msg
|
||||||
|
* @param {CB<Rec>} cb
|
||||||
|
*/
|
||||||
function logAppend(hash, msg, cb) {
|
function logAppend(hash, msg, cb) {
|
||||||
|
/** @type {RecPresent} */
|
||||||
const rec = {
|
const rec = {
|
||||||
hash,
|
hash,
|
||||||
msg,
|
msg,
|
||||||
received: Date.now(),
|
received: Date.now(),
|
||||||
|
misc: {
|
||||||
|
offset: 0,
|
||||||
|
size: 0,
|
||||||
|
seq: 0,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
log.append(rec, (err, newOffset) => {
|
log.append(
|
||||||
|
rec,
|
||||||
|
(/** @type {any} */ err, /** @type {number} */ newOffset) => {
|
||||||
if (err) return cb(new Error('logAppend failed', { cause: err }))
|
if (err) return cb(new Error('logAppend failed', { cause: err }))
|
||||||
const offset = newOffset // latestOffset
|
const offset = newOffset // latestOffset
|
||||||
const size = b4a.from(JSON.stringify(rec), 'utf8').length
|
const size = b4a.from(JSON.stringify(rec), 'utf8').length
|
||||||
|
@ -148,13 +198,17 @@ exports.init = function initDB(peer, config) {
|
||||||
rec.misc = recExposed.misc = { offset, size, seq }
|
rec.misc = recExposed.misc = { offset, size, seq }
|
||||||
recs.push(recExposed)
|
recs.push(recExposed)
|
||||||
cb(null, rec)
|
cb(null, rec)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {EncryptionFormat} encryptionFormat
|
||||||
|
*/
|
||||||
function installEncryptionFormat(encryptionFormat) {
|
function installEncryptionFormat(encryptionFormat) {
|
||||||
if (encryptionFormat.setup) {
|
if (encryptionFormat.setup) {
|
||||||
const loaded = new ReadyGate()
|
const loaded = new ReadyGate()
|
||||||
encryptionFormat.setup(config, (err) => {
|
encryptionFormat.setup(config, (/** @type {any} */ err) => {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
if (err) throw new Error(`Failed to install encryption format "${encryptionFormat.name}"`, {cause: err});
|
if (err) throw new Error(`Failed to install encryption format "${encryptionFormat.name}"`, {cause: err});
|
||||||
loaded.setReady()
|
loaded.setReady()
|
||||||
|
@ -164,15 +218,27 @@ exports.init = function initDB(peer, config) {
|
||||||
encryptionFormats.set(encryptionFormat.name, encryptionFormat)
|
encryptionFormats.set(encryptionFormat.name, encryptionFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} ciphertextJS
|
||||||
|
*/
|
||||||
function findEncryptionFormatFor(ciphertextJS) {
|
function findEncryptionFormatFor(ciphertextJS) {
|
||||||
if (!ciphertextJS) return null
|
if (!ciphertextJS) return null
|
||||||
if (typeof ciphertextJS !== 'string') return null
|
if (typeof ciphertextJS !== 'string') return null
|
||||||
const suffix = ciphertextJS.split('.').pop()
|
const suffix = ciphertextJS.split('.').pop()
|
||||||
|
if (!suffix) {
|
||||||
|
// prettier-ignore
|
||||||
|
console.warn('findEncryptionFormatFor() failed to find suffix\n\n' + ciphertextJS)
|
||||||
|
return null
|
||||||
|
}
|
||||||
const encryptionFormat = encryptionFormats.get(suffix) ?? null
|
const encryptionFormat = encryptionFormats.get(suffix) ?? null
|
||||||
return encryptionFormat
|
return encryptionFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array<string>} tangleIds
|
||||||
|
*/
|
||||||
function populateTangles(tangleIds) {
|
function populateTangles(tangleIds) {
|
||||||
|
/** @type {Record<string, DBTangle>} */
|
||||||
const tangles = {}
|
const tangles = {}
|
||||||
for (const tangleId of tangleIds) {
|
for (const tangleId of tangleIds) {
|
||||||
tangles[tangleId] ??= new DBTangle(tangleId, records())
|
tangles[tangleId] ??= new DBTangle(tangleId, records())
|
||||||
|
@ -180,11 +246,19 @@ exports.init = function initDB(peer, config) {
|
||||||
return tangles
|
return tangles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {CB<void>} cb
|
||||||
|
*/
|
||||||
function loaded(cb) {
|
function loaded(cb) {
|
||||||
if (cb === void 0) return promisify(loaded)()
|
if (cb === void 0) return promisify(loaded)()
|
||||||
scannedLog.onReady(cb)
|
scannedLog.onReady(cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Msg} msg
|
||||||
|
* @param {string} tangleRootHash
|
||||||
|
* @param {CB<Rec>} cb
|
||||||
|
*/
|
||||||
function add(msg, tangleRootHash, cb) {
|
function add(msg, tangleRootHash, cb) {
|
||||||
const msgHash = MsgV2.getMsgHash(msg)
|
const msgHash = MsgV2.getMsgHash(msg)
|
||||||
|
|
||||||
|
@ -223,6 +297,10 @@ exports.init = function initDB(peer, config) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ keypair?: any; identity: string; domain: string; }} opts
|
||||||
|
* @param {CB<string>} cb
|
||||||
|
*/
|
||||||
function initializeFeed(opts, cb) {
|
function initializeFeed(opts, cb) {
|
||||||
const keypair = opts.keypair ?? config.keypair
|
const keypair = opts.keypair ?? config.keypair
|
||||||
const { identity, domain } = opts
|
const { identity, domain } = opts
|
||||||
|
@ -234,10 +312,15 @@ exports.init = function initDB(peer, config) {
|
||||||
add(feedRoot, MsgV2.getMsgHash(feedRoot), (err, rec) => {
|
add(feedRoot, MsgV2.getMsgHash(feedRoot), (err, rec) => {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
if (err) return cb(new Error('initializeFeed() failed to add root', { cause: err }));
|
if (err) return cb(new Error('initializeFeed() failed to add root', { cause: err }));
|
||||||
cb(null, rec.hash)
|
const recHash = /** @type {string} */ (rec.hash)
|
||||||
|
cb(null, recHash)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{keypair?: Keypair, _nonce?: string} | null} opts
|
||||||
|
* @param {CB<Rec>} cb
|
||||||
|
*/
|
||||||
function createIdentity(opts, cb) {
|
function createIdentity(opts, cb) {
|
||||||
const keypair = opts?.keypair ?? config.keypair
|
const keypair = opts?.keypair ?? config.keypair
|
||||||
|
|
||||||
|
@ -257,9 +340,15 @@ exports.init = function initDB(peer, config) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ keypair: Keypair; identity: string; }} opts
|
||||||
|
* @param {CB<Rec>} cb
|
||||||
|
*/
|
||||||
function addToIdentity(opts, cb) {
|
function addToIdentity(opts, cb) {
|
||||||
if (!opts?.keypair) return cb(new Error('identity.add() requires a `keypair`'))
|
if (!opts?.keypair)
|
||||||
if (!opts?.identity) return cb(new Error('identity.add() requires a `identity`'))
|
return cb(new Error('identity.add() requires a `keypair`'))
|
||||||
|
if (!opts?.identity)
|
||||||
|
return cb(new Error('identity.add() requires a `identity`'))
|
||||||
const addedKeypair = opts.keypair
|
const addedKeypair = opts.keypair
|
||||||
const signingKeypair = config.keypair
|
const signingKeypair = config.keypair
|
||||||
|
|
||||||
|
@ -291,20 +380,31 @@ exports.init = function initDB(peer, config) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{
|
||||||
|
* keypair?: Keypair;
|
||||||
|
* encryptionFormat?: string;
|
||||||
|
* data: any;
|
||||||
|
* domain: string;
|
||||||
|
* identity: string;
|
||||||
|
* tangles?: Array<string>;
|
||||||
|
* }} opts
|
||||||
|
* @param {CB<Rec>} cb
|
||||||
|
*/
|
||||||
function publishToFeed(opts, cb) {
|
function publishToFeed(opts, cb) {
|
||||||
if (!opts) return cb(new Error('feed.publish() requires an `opts`'))
|
if (!opts) return cb(new Error('feed.publish() requires an `opts`'))
|
||||||
const keypair = opts.keypair ?? config.keypair
|
const keypair = opts.keypair ?? config.keypair
|
||||||
|
|
||||||
const encryptionFormat = encryptionFormats.get(opts.encryptionFormat)
|
|
||||||
if (opts.data.recps) {
|
if (opts.data.recps) {
|
||||||
if (!encryptionFormat) {
|
if (!encryptionFormats.has(opts.encryptionFormat ?? '')) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return cb(new Error(`feed.publish() does not support encryption format "${opts.encryptionFormat}"`))
|
return cb(new Error(`feed.publish() does not support encryption format "${opts.encryptionFormat}"`))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!opts.data) return cb(new Error('feed.publish() requires a `data`'))
|
if (!opts.data) return cb(new Error('feed.publish() requires a `data`'))
|
||||||
if (!opts.domain) return cb(new Error('feed.publish() requires a `domain`'))
|
if (!opts.domain) return cb(new Error('feed.publish() requires a `domain`'))
|
||||||
if (!opts.identity) return cb(new Error('feed.publish() requires a `identity`'))
|
if (!opts.identity)
|
||||||
|
return cb(new Error('feed.publish() requires a `identity`'))
|
||||||
|
|
||||||
initializeFeed(opts, (err, feedRootHash) => {
|
initializeFeed(opts, (err, feedRootHash) => {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
|
@ -330,6 +430,9 @@ exports.init = function initDB(peer, config) {
|
||||||
`@${b4a.from(base58.decode(recp)).toString('base64')}.ed25519`
|
`@${b4a.from(base58.decode(recp)).toString('base64')}.ed25519`
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
const encryptionFormat = /** @type {EncryptionFormat} */ (
|
||||||
|
encryptionFormats.get(opts.encryptionFormat ?? '')
|
||||||
|
)
|
||||||
let ciphertextBuf
|
let ciphertextBuf
|
||||||
try {
|
try {
|
||||||
ciphertextBuf = encryptionFormat.encrypt(plaintext, encryptOpts)
|
ciphertextBuf = encryptionFormat.encrypt(plaintext, encryptOpts)
|
||||||
|
@ -367,31 +470,47 @@ exports.init = function initDB(peer, config) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
* @param {string} findDomain
|
||||||
|
*/
|
||||||
function getFeedId(id, findDomain) {
|
function getFeedId(id, findDomain) {
|
||||||
const findIdentity = MsgV2.stripIdentity(id)
|
const findIdentity = MsgV2.stripIdentity(id)
|
||||||
for (const rec of records()) {
|
for (const rec of records()) {
|
||||||
if (MsgV2.isFeedRoot(rec.msg, findIdentity, findDomain)) return rec.hash
|
if (rec.msg && MsgV2.isFeedRoot(rec.msg, findIdentity, findDomain)) {
|
||||||
|
return rec.hash
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: improve performance of this when getting many messages, the argument
|
/**
|
||||||
// could be an array of hashes, so we can do a single pass over the records.
|
* @param {string} msgId
|
||||||
|
*/
|
||||||
function getRecord(msgId) {
|
function getRecord(msgId) {
|
||||||
|
// TODO: improve performance of this when getting many messages, the arg
|
||||||
|
// could be an array of hashes, so we can do a single pass over the records.
|
||||||
const isUri = msgId.startsWith('ppppp:')
|
const isUri = msgId.startsWith('ppppp:')
|
||||||
for (let i = 0; i < recs.length; i++) {
|
for (let i = 0; i < recs.length; i++) {
|
||||||
const rec = recs[i]
|
const rec = recs[i]
|
||||||
if (!rec) continue
|
if (!rec) continue
|
||||||
if (isUri && msgId.endsWith(rec.hash)) return rec
|
if (isUri && rec.hash && msgId.endsWith(rec.hash)) return rec
|
||||||
else if (!isUri && rec.hash === msgId) return rec
|
else if (!isUri && rec.hash === msgId) return rec
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} msgId
|
||||||
|
*/
|
||||||
function get(msgId) {
|
function get(msgId) {
|
||||||
return getRecord(msgId)?.msg
|
return getRecord(msgId)?.msg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} msgId
|
||||||
|
* @param {CBVoid} cb
|
||||||
|
*/
|
||||||
function del(msgId, cb) {
|
function del(msgId, cb) {
|
||||||
const rec = getRecord(msgId)
|
const rec = getRecord(msgId)
|
||||||
if (!rec) return cb()
|
if (!rec) return cb()
|
||||||
|
@ -403,6 +522,10 @@ exports.init = function initDB(peer, config) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} msgId
|
||||||
|
* @param {CBVoid} cb
|
||||||
|
*/
|
||||||
function erase(msgId, cb) {
|
function erase(msgId, cb) {
|
||||||
const rec = getRecord(msgId)
|
const rec = getRecord(msgId)
|
||||||
if (!rec) return cb()
|
if (!rec) return cb()
|
||||||
|
@ -413,6 +536,10 @@ exports.init = function initDB(peer, config) {
|
||||||
cb()
|
cb()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} tangleId
|
||||||
|
* @returns {DBTangle}
|
||||||
|
*/
|
||||||
function getTangle(tangleId) {
|
function getTangle(tangleId) {
|
||||||
return new DBTangle(tangleId, records())
|
return new DBTangle(tangleId, records())
|
||||||
}
|
}
|
||||||
|
@ -460,3 +587,6 @@ exports.init = function initDB(peer, config) {
|
||||||
_getLog: () => log,
|
_getLog: () => log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.name = 'db'
|
||||||
|
exports.init = initDB
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const b4a = require('b4a')
|
const b4a = require('b4a')
|
||||||
const blake3 = require('blake3')
|
const blake3 = require('blake3')
|
||||||
const base58 = require('bs58')
|
const base58 = require('bs58')
|
||||||
|
// @ts-ignore
|
||||||
const stringify = require('json-canon')
|
const stringify = require('json-canon')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,7 +15,8 @@ const stringify = require('json-canon')
|
||||||
*/
|
*/
|
||||||
function getMsgHashBuf(msg) {
|
function getMsgHashBuf(msg) {
|
||||||
const metadataBuf = b4a.from(stringify(msg.metadata), 'utf8')
|
const metadataBuf = b4a.from(stringify(msg.metadata), 'utf8')
|
||||||
return blake3.hash(metadataBuf).subarray(0, 16)
|
const longHash = b4a.from(blake3.hash(metadataBuf))
|
||||||
|
return longHash.subarray(0, 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
const crypto = require('node:crypto')
|
const crypto = require('node:crypto')
|
||||||
const base58 = require('bs58')
|
const base58 = require('bs58')
|
||||||
const b4a = require('b4a')
|
const b4a = require('b4a')
|
||||||
|
// @ts-ignore
|
||||||
const stringify = require('json-canon')
|
const stringify = require('json-canon')
|
||||||
const Keypair = require('ppppp-keypair')
|
const Keypair = require('ppppp-keypair')
|
||||||
|
// @ts-ignore
|
||||||
const union = require('set.prototype.union')
|
const union = require('set.prototype.union')
|
||||||
const { stripIdentity } = require('./strip')
|
const { stripIdentity } = require('./strip')
|
||||||
const isFeedRoot = require('./is-feed-root')
|
const isFeedRoot = require('./is-feed-root')
|
||||||
|
@ -12,7 +14,6 @@ const {
|
||||||
validateDomain,
|
validateDomain,
|
||||||
validateData,
|
validateData,
|
||||||
validate,
|
validate,
|
||||||
validateBatch,
|
|
||||||
validateMsgHash,
|
validateMsgHash,
|
||||||
} = require('./validation')
|
} = require('./validation')
|
||||||
const Tangle = require('./tangle')
|
const Tangle = require('./tangle')
|
||||||
|
@ -56,6 +57,11 @@ const Tangle = require('./tangle')
|
||||||
* }} CreateOpts
|
* }} CreateOpts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
* @param {string} domain
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
function getFeedRootHash(id, domain) {
|
function getFeedRootHash(id, domain) {
|
||||||
/** @type {Msg} */
|
/** @type {Msg} */
|
||||||
const msg = {
|
const msg = {
|
||||||
|
@ -76,6 +82,10 @@ function getFeedRootHash(id, domain) {
|
||||||
return getMsgHash(msg)
|
return getMsgHash(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Pick<CreateOpts, 'data'>} opts
|
||||||
|
* @returns {B4A}
|
||||||
|
*/
|
||||||
function toPlaintextBuffer(opts) {
|
function toPlaintextBuffer(opts) {
|
||||||
return b4a.from(stringify(opts.data), 'utf8')
|
return b4a.from(stringify(opts.data), 'utf8')
|
||||||
}
|
}
|
||||||
|
@ -93,7 +103,7 @@ function create(opts) {
|
||||||
const identity = opts.identity ? stripIdentity(opts.identity) : null
|
const identity = opts.identity ? stripIdentity(opts.identity) : null
|
||||||
const identityTips = opts.identityTips ? opts.identityTips.sort() : null
|
const identityTips = opts.identityTips ? opts.identityTips.sort() : null
|
||||||
|
|
||||||
const tangles = {}
|
const tangles = /** @type {Msg['metadata']['tangles']} */ ({})
|
||||||
if (opts.tangles) {
|
if (opts.tangles) {
|
||||||
for (const rootId in opts.tangles) {
|
for (const rootId in opts.tangles) {
|
||||||
if ((err = validateMsgHash(rootId))) throw err
|
if ((err = validateMsgHash(rootId))) throw err
|
||||||
|
@ -168,7 +178,7 @@ function createRoot(id, domain, keypair) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Keypair} keypair
|
* @param {Keypair} keypair
|
||||||
* @param {string} nonce
|
* @param {string | (() => string)} nonce
|
||||||
* @returns {Msg}
|
* @returns {Msg}
|
||||||
*/
|
*/
|
||||||
function createIdentity(
|
function createIdentity(
|
||||||
|
@ -217,5 +227,4 @@ module.exports = {
|
||||||
fromPlaintextBuffer,
|
fromPlaintextBuffer,
|
||||||
Tangle,
|
Tangle,
|
||||||
validate,
|
validate,
|
||||||
validateBatch,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
const { stripIdentity } = require('./strip')
|
const { stripIdentity } = require('./strip')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('.').Msg} Msg
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} obj
|
||||||
|
*/
|
||||||
function isEmptyObject(obj) {
|
function isEmptyObject(obj) {
|
||||||
for (const _key in obj) {
|
for (const _key in obj) {
|
||||||
return false
|
return false
|
||||||
|
@ -7,6 +14,11 @@ function isEmptyObject(obj) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Msg} msg
|
||||||
|
* @param {string | 0} id
|
||||||
|
* @param {string | 0} findDomain
|
||||||
|
*/
|
||||||
function isFeedRoot(msg, id = 0, findDomain = 0) {
|
function isFeedRoot(msg, id = 0, findDomain = 0) {
|
||||||
const { dataHash, dataSize, identity, identityTips, tangles, domain } = msg.metadata
|
const { dataHash, dataSize, identity, identityTips, tangles, domain } = msg.metadata
|
||||||
if (dataHash !== null) return false
|
if (dataHash !== null) return false
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
const blake3 = require('blake3')
|
const blake3 = require('blake3')
|
||||||
const b4a = require('b4a')
|
const b4a = require('b4a')
|
||||||
const base58 = require('bs58')
|
const base58 = require('bs58')
|
||||||
|
// @ts-ignore
|
||||||
const stringify = require('json-canon')
|
const stringify = require('json-canon')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Buffer | Uint8Array} B4A
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {any} data
|
* @param {any} data
|
||||||
* @returns {[string, number]}
|
* @returns {[string, number]}
|
||||||
*/
|
*/
|
||||||
function representData(data) {
|
function representData(data) {
|
||||||
const dataBuf = b4a.from(stringify(data), 'utf8')
|
const dataBuf = b4a.from(stringify(data), 'utf8')
|
||||||
const dataHash = base58.encode(blake3.hash(dataBuf).subarray(0, 16))
|
const fullHash = /** @type {B4A} */ (blake3.hash(dataBuf))
|
||||||
|
const dataHash = base58.encode(fullHash.subarray(0, 16))
|
||||||
const dataSize = dataBuf.length
|
const dataSize = dataBuf.length
|
||||||
return [dataHash, dataSize]
|
return [dataHash, dataSize]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
const { getMsgHash } = require('./get-msg-id')
|
const { getMsgHash } = require('./get-msg-id')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('.').Msg} Msg
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} msgKey
|
||||||
|
*/
|
||||||
function stripMsgKey(msgKey) {
|
function stripMsgKey(msgKey) {
|
||||||
if (typeof msgKey === 'object') {
|
if (typeof msgKey === 'object') {
|
||||||
if (msgKey.key) return stripMsgKey(msgKey.key)
|
if (msgKey.key) return stripMsgKey(msgKey.key)
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
* @typedef {import("./index").Msg} Msg
|
* @typedef {import("./index").Msg} Msg
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} n
|
||||||
|
*/
|
||||||
function lipmaa(n) {
|
function lipmaa(n) {
|
||||||
let m = 1
|
let m = 1
|
||||||
let po3 = 3
|
let po3 = 3
|
||||||
|
@ -46,7 +49,7 @@ class Tangle {
|
||||||
#rootHash
|
#rootHash
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Msg}
|
* @type {Msg | undefined}
|
||||||
*/
|
*/
|
||||||
#rootMsg
|
#rootMsg
|
||||||
|
|
||||||
|
@ -77,13 +80,16 @@ class Tangle {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} rootHash
|
* @param {string} rootHash
|
||||||
* @param {Iterable<Msg>} msgsIter
|
|
||||||
*/
|
*/
|
||||||
constructor(rootHash) {
|
constructor(rootHash) {
|
||||||
this.#rootHash = rootHash
|
this.#rootHash = rootHash
|
||||||
this.#maxDepth = 0
|
this.#maxDepth = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} msgHash
|
||||||
|
* @param {Msg} msg
|
||||||
|
*/
|
||||||
add(msgHash, msg) {
|
add(msgHash, msg) {
|
||||||
if (msgHash === this.#rootHash && !this.#rootMsg) {
|
if (msgHash === this.#rootHash && !this.#rootMsg) {
|
||||||
this.#tips.add(msgHash)
|
this.#tips.add(msgHash)
|
||||||
|
@ -195,10 +201,17 @@ class Tangle {
|
||||||
|
|
||||||
getFeed() {
|
getFeed() {
|
||||||
if (!this.isFeed()) return null
|
if (!this.isFeed()) return null
|
||||||
|
if (!this.#rootMsg) {
|
||||||
|
console.trace('Tangle is missing root message')
|
||||||
|
return null
|
||||||
|
}
|
||||||
const { identity, domain } = this.#rootMsg.metadata
|
const { identity, domain } = this.#rootMsg.metadata
|
||||||
return { identity, domain }
|
return { identity, domain }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} msgHash
|
||||||
|
*/
|
||||||
shortestPathToRoot(msgHash) {
|
shortestPathToRoot(msgHash) {
|
||||||
if (!this.#rootMsg) {
|
if (!this.#rootMsg) {
|
||||||
console.trace('Tangle is missing root message')
|
console.trace('Tangle is missing root message')
|
||||||
|
@ -209,10 +222,10 @@ class Tangle {
|
||||||
while (true) {
|
while (true) {
|
||||||
const prev = this.#prev.get(current)
|
const prev = this.#prev.get(current)
|
||||||
if (!prev) break
|
if (!prev) break
|
||||||
let minDepth = this.#depth.get(current)
|
let minDepth = /** @type {number} */ (this.#depth.get(current))
|
||||||
let min = current
|
let min = current
|
||||||
for (const p of prev) {
|
for (const p of prev) {
|
||||||
const d = this.#depth.get(p)
|
const d = /** @type {number} */ (this.#depth.get(p))
|
||||||
if (d < minDepth) {
|
if (d < minDepth) {
|
||||||
minDepth = d
|
minDepth = d
|
||||||
min = p
|
min = p
|
||||||
|
@ -226,18 +239,22 @@ class Tangle {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
precedes(a, b) {
|
/**
|
||||||
|
* @param {string} msgHashA
|
||||||
|
* @param {string} msgHashB
|
||||||
|
*/
|
||||||
|
precedes(msgHashA, msgHashB) {
|
||||||
if (!this.#rootMsg) {
|
if (!this.#rootMsg) {
|
||||||
console.trace('Tangle is missing root message')
|
console.trace('Tangle is missing root message')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (a === b) return false
|
if (msgHashA === msgHashB) return false
|
||||||
if (b === this.#rootHash) return false
|
if (msgHashB === this.#rootHash) return false
|
||||||
let toCheck = [b]
|
let toCheck = [msgHashB]
|
||||||
while (toCheck.length > 0) {
|
while (toCheck.length > 0) {
|
||||||
const prev = this.#prev.get(toCheck.shift())
|
const prev = this.#prev.get(/** @type {string} */ (toCheck.shift()))
|
||||||
if (!prev) continue
|
if (!prev) continue
|
||||||
if (prev.includes(a)) return true
|
if (prev.includes(msgHashA)) return true
|
||||||
toCheck.push(...prev)
|
toCheck.push(...prev)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -1,11 +1,20 @@
|
||||||
const b4a = require('b4a')
|
const b4a = require('b4a')
|
||||||
const base58 = require('bs58')
|
const base58 = require('bs58')
|
||||||
const Keypair = require('ppppp-keypair')
|
const Keypair = require('ppppp-keypair')
|
||||||
|
// @ts-ignore
|
||||||
const stringify = require('json-canon')
|
const stringify = require('json-canon')
|
||||||
const Tangle = require('./tangle')
|
const Tangle = require('./tangle')
|
||||||
const representData = require('./represent-data')
|
const representData = require('./represent-data')
|
||||||
const isFeedRoot = require('./is-feed-root')
|
const isFeedRoot = require('./is-feed-root')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('.').Msg} Msg
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Msg} msg
|
||||||
|
* @returns {string | undefined}
|
||||||
|
*/
|
||||||
function validateShape(msg) {
|
function validateShape(msg) {
|
||||||
if (!msg || typeof msg !== 'object') {
|
if (!msg || typeof msg !== 'object') {
|
||||||
return 'invalid message: not an object\n' + JSON.stringify(msg)
|
return 'invalid message: not an object\n' + JSON.stringify(msg)
|
||||||
|
@ -25,7 +34,9 @@ function validateShape(msg) {
|
||||||
return 'invalid message: must have metadata.dataSize\n' + JSON.stringify(msg)
|
return 'invalid message: must have metadata.dataSize\n' + JSON.stringify(msg)
|
||||||
}
|
}
|
||||||
if (!('identity' in msg.metadata)) {
|
if (!('identity' in msg.metadata)) {
|
||||||
return 'invalid message: must have metadata.identity\n' + JSON.stringify(msg)
|
return (
|
||||||
|
'invalid message: must have metadata.identity\n' + JSON.stringify(msg)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (!('identityTips' in msg.metadata)) {
|
if (!('identityTips' in msg.metadata)) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
|
@ -45,6 +56,10 @@ function validateShape(msg) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Msg} msg
|
||||||
|
* @returns {string | undefined}
|
||||||
|
*/
|
||||||
function validatePubkey(msg) {
|
function validatePubkey(msg) {
|
||||||
const { pubkey } = msg
|
const { pubkey } = msg
|
||||||
if (typeof pubkey !== 'string') {
|
if (typeof pubkey !== 'string') {
|
||||||
|
@ -63,6 +78,12 @@ function validatePubkey(msg) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Msg} msg
|
||||||
|
* @param {Set<string>} pubkeys
|
||||||
|
* @returns {string | undefined}
|
||||||
|
*/
|
||||||
function validateIdentityPubkey(msg, pubkeys) {
|
function validateIdentityPubkey(msg, pubkeys) {
|
||||||
// Unusual case: if the msg is a feed root, ignore the identity and pubkey
|
// Unusual case: if the msg is a feed root, ignore the identity and pubkey
|
||||||
if (isFeedRoot(msg)) return
|
if (isFeedRoot(msg)) return
|
||||||
|
@ -73,6 +94,10 @@ function validateIdentityPubkey(msg, pubkeys) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} str
|
||||||
|
* @returns {string | undefined}
|
||||||
|
*/
|
||||||
function validateMsgHash(str) {
|
function validateMsgHash(str) {
|
||||||
try {
|
try {
|
||||||
const hashBuf = b4a.from(base58.decode(str))
|
const hashBuf = b4a.from(base58.decode(str))
|
||||||
|
@ -85,6 +110,10 @@ function validateMsgHash(str) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Msg} msg
|
||||||
|
* @returns {string | undefined}
|
||||||
|
*/
|
||||||
function validateDataSize(msg) {
|
function validateDataSize(msg) {
|
||||||
const { dataSize } = msg.metadata
|
const { dataSize } = msg.metadata
|
||||||
if (!Number.isSafeInteger(dataSize) || dataSize < 0) {
|
if (!Number.isSafeInteger(dataSize) || dataSize < 0) {
|
||||||
|
@ -93,6 +122,10 @@ function validateDataSize(msg) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Msg} msg
|
||||||
|
* @returns {string | undefined}
|
||||||
|
*/
|
||||||
function validateSignature(msg) {
|
function validateSignature(msg) {
|
||||||
const { sig } = msg
|
const { sig } = msg
|
||||||
if (typeof sig !== 'string') {
|
if (typeof sig !== 'string') {
|
||||||
|
@ -114,7 +147,7 @@ function validateSignature(msg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const signableBuf = b4a.from(stringify(msg.metadata), 'utf8')
|
const signableBuf = b4a.from(stringify(msg.metadata), 'utf8')
|
||||||
const keypair = {curve: 'ed25519', public: msg.pubkey}
|
const keypair = { curve: 'ed25519', public: msg.pubkey }
|
||||||
const verified = Keypair.verify(keypair, signableBuf, sig)
|
const verified = Keypair.verify(keypair, signableBuf, sig)
|
||||||
if (!verified) {
|
if (!verified) {
|
||||||
return 'invalid message: sig is invalid\n' + JSON.stringify(msg)
|
return 'invalid message: sig is invalid\n' + JSON.stringify(msg)
|
||||||
|
@ -122,10 +155,13 @@ function validateSignature(msg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* @typedef {NonNullable<ReturnType<Tangle['getFeed']>>} FeedDetails
|
||||||
* @param {any} msg
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Msg} msg
|
||||||
* @param {Tangle} tangle
|
* @param {Tangle} tangle
|
||||||
* @param {*} tangleId
|
* @param {string} tangleId
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function validateTangle(msg, tangle, tangleId) {
|
function validateTangle(msg, tangle, tangleId) {
|
||||||
|
@ -143,7 +179,7 @@ function validateTangle(msg, tangle, tangleId) {
|
||||||
return `invalid message: depth "${depth}" should have been a positive integer\n` + JSON.stringify(msg)
|
return `invalid message: depth "${depth}" should have been a positive integer\n` + JSON.stringify(msg)
|
||||||
}
|
}
|
||||||
if (tangle.isFeed()) {
|
if (tangle.isFeed()) {
|
||||||
const { identity, domain } = tangle.getFeed()
|
const { identity, domain } = /** @type {FeedDetails} */ (tangle.getFeed())
|
||||||
if (domain !== msg.metadata.domain) {
|
if (domain !== msg.metadata.domain) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return `invalid message: domain "${msg.metadata.domain}" should have been feed domain "${domain}"\n` + JSON.stringify(msg)
|
return `invalid message: domain "${msg.metadata.domain}" should have been feed domain "${domain}"\n` + JSON.stringify(msg)
|
||||||
|
@ -202,6 +238,11 @@ function validateTangle(msg, tangle, tangleId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Msg} msg
|
||||||
|
* @param {string} msgHash
|
||||||
|
* @param {string} tangleId
|
||||||
|
*/
|
||||||
function validateTangleRoot(msg, msgHash, tangleId) {
|
function validateTangleRoot(msg, msgHash, tangleId) {
|
||||||
if (msgHash !== tangleId) {
|
if (msgHash !== tangleId) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
|
@ -213,6 +254,9 @@ function validateTangleRoot(msg, msgHash, tangleId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} domain
|
||||||
|
*/
|
||||||
function validateDomain(domain) {
|
function validateDomain(domain) {
|
||||||
if (!domain || typeof domain !== 'string') {
|
if (!domain || typeof domain !== 'string') {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
|
@ -232,6 +276,9 @@ function validateDomain(domain) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Msg} msg
|
||||||
|
*/
|
||||||
function validateData(msg) {
|
function validateData(msg) {
|
||||||
const { data } = msg
|
const { data } = msg
|
||||||
if (data === null) {
|
if (data === null) {
|
||||||
|
@ -260,6 +307,13 @@ function validateData(msg) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Msg} msg
|
||||||
|
* @param {Tangle} tangle
|
||||||
|
* @param {Set<string>} pubkeys
|
||||||
|
* @param {string} msgHash
|
||||||
|
* @param {string} rootHash
|
||||||
|
*/
|
||||||
function validate(msg, tangle, pubkeys, msgHash, rootHash) {
|
function validate(msg, tangle, pubkeys, msgHash, rootHash) {
|
||||||
let err
|
let err
|
||||||
if ((err = validateShape(msg))) return err
|
if ((err = validateShape(msg))) return err
|
||||||
|
|
|
@ -6,6 +6,9 @@ class ReadyGate {
|
||||||
this.#ready = false
|
this.#ready = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {() => void} cb
|
||||||
|
*/
|
||||||
onReady(cb) {
|
onReady(cb) {
|
||||||
if (this.#ready) cb()
|
if (this.#ready) cb()
|
||||||
else this.#waiting.add(cb)
|
else this.#waiting.add(cb)
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
"files": [
|
"files": [
|
||||||
"lib/**/*"
|
"lib/**/*"
|
||||||
],
|
],
|
||||||
|
"types": "types/index.d.ts",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
},
|
},
|
||||||
|
@ -46,9 +47,11 @@
|
||||||
"pretty-quick": "^3.1.3",
|
"pretty-quick": "^3.1.3",
|
||||||
"rimraf": "^4.4.0",
|
"rimraf": "^4.4.0",
|
||||||
"secret-stack": "^6.4.2",
|
"secret-stack": "^6.4.2",
|
||||||
"ssb-box": "^1.0.1"
|
"ssb-box": "^1.0.1",
|
||||||
|
"typescript": "^5.1.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"build": "tsc --build --clean && tsc --build",
|
||||||
"test": "node --test",
|
"test": "node --test",
|
||||||
"format-code": "prettier --write \"(lib|test)/**/*.js\"",
|
"format-code": "prettier --write \"(lib|test)/**/*.js\"",
|
||||||
"format-code-staged": "pretty-quick --staged --pattern \"(lib|test)/**/*.js\"",
|
"format-code-staged": "pretty-quick --staged --pattern \"(lib|test)/**/*.js\"",
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"include": ["lib/**/*.js"],
|
||||||
|
"exclude": ["coverage/", "node_modules/", "test/"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"checkJs": true,
|
||||||
|
"declaration": true,
|
||||||
|
"emitDeclarationOnly": true,
|
||||||
|
"exactOptionalPropertyTypes": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"lib": ["es2022", "dom"],
|
||||||
|
"module": "node16",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"target": "es2021"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue