mirror of https://codeberg.org/pzp/pzp-promise.git
update to ppppp-db in 2024
This commit is contained in:
parent
05d0523d1d
commit
85d594591a
99
lib/index.js
99
lib/index.js
|
@ -6,7 +6,14 @@ const bs58 = require('bs58')
|
||||||
const b4a = require('b4a')
|
const b4a = require('b4a')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('ppppp-db/msg-v3').AccountAdd} AccountAdd
|
* @typedef {ReturnType<import('ppppp-db').init>} PPPPPDB
|
||||||
|
* @typedef {import('ppppp-db/msg-v4').AccountAdd} AccountAdd
|
||||||
|
* @typedef {Buffer | Uint8Array} B4A
|
||||||
|
* @typedef {{global: {path: string}}} ExpectedConfig
|
||||||
|
* @typedef {{global: {path?: string}}} Config
|
||||||
|
* @typedef {{type: 'follow'}} FollowPromise
|
||||||
|
* @typedef {{type: 'account-add', account: string}} AccountAddPromise
|
||||||
|
* @typedef {FollowPromise | AccountAddPromise} PPromise
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,16 +22,23 @@ const b4a = require('b4a')
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Buffer | Uint8Array} B4A
|
* @param {{ db: PPPPPDB | null }} peer
|
||||||
|
* @returns {asserts peer is { db: PPPPPDB }}
|
||||||
*/
|
*/
|
||||||
|
function assertDBPlugin(peer) {
|
||||||
|
// prettier-ignore
|
||||||
|
if (!peer.db) throw new Error('promise plugin plugin requires ppppp-db plugin')
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {{type: 'follow'}} FollowPromise
|
* @param {Config} config
|
||||||
* @typedef {{type: 'account-add', account: string}} AccountAddPromise
|
* @returns {asserts config is ExpectedConfig}
|
||||||
* @typedef {FollowPromise | AccountAddPromise} PPromise
|
|
||||||
*/
|
*/
|
||||||
|
function assertValidConfig(config) {
|
||||||
const FILENAME = 'promises.json'
|
if (typeof config.global?.path !== 'string') {
|
||||||
|
throw new Error('promise plugin requires config.global.path')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name: 'promise',
|
name: 'promise',
|
||||||
|
@ -43,11 +57,13 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {any} local
|
* @param {{ db: PPPPPDB | null }} peer
|
||||||
* @param {any} config
|
* @param {Config} config
|
||||||
*/
|
*/
|
||||||
init(local, config) {
|
init(peer, config) {
|
||||||
const devicePromisesFile = Path.join(config.path, FILENAME)
|
assertDBPlugin(peer)
|
||||||
|
assertValidConfig(config)
|
||||||
|
const devicePromisesFile = Path.join(config.global.path, 'promises.json')
|
||||||
|
|
||||||
const promises = /** @type {Map<string, PPromise>} */ (new Map())
|
const promises = /** @type {Map<string, PPromise>} */ (new Map())
|
||||||
let loaded = false
|
let loaded = false
|
||||||
|
@ -164,22 +180,60 @@ module.exports = {
|
||||||
setTimeout(() => accountAdd(token, addition, cb), 100)
|
setTimeout(() => accountAdd(token, addition, cb), 100)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
assertDBPlugin(peer)
|
||||||
|
} catch (err) {
|
||||||
|
cb(/**@type {Error}*/ (err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!addition?.consent) {
|
||||||
|
// prettier-ignore
|
||||||
|
cb(new Error('Invalid key to be added, missing "consent": ' + JSON.stringify(addition)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!addition?.key?.purpose ||
|
!addition?.key?.purpose ||
|
||||||
!addition?.key?.algorithm ||
|
!addition?.key?.algorithm ||
|
||||||
!addition?.key?.bytes ||
|
!addition?.key?.bytes
|
||||||
!addition?.consent ||
|
|
||||||
addition?.key?.purpose !== 'sig' ||
|
|
||||||
addition?.key?.algorithm !== 'ed25519'
|
|
||||||
) {
|
) {
|
||||||
cb(new Error('Invalid key to be added: ' + JSON.stringify(addition)))
|
// prettier-ignore
|
||||||
|
cb(new Error('Invalid key to be added, missing purpose/algorithm/bytes: ' + JSON.stringify(addition)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { algorithm, purpose } = addition.key
|
||||||
|
switch (purpose) {
|
||||||
|
case 'sig':
|
||||||
|
case 'shs-and-sig':
|
||||||
|
if (algorithm !== 'ed25519') {
|
||||||
|
// prettier-ignore
|
||||||
|
cb(new Error(`Invalid key to be added, expected algorithm "ed25519" for "${purpose}": ${JSON.stringify(addition)}`))
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'external-encryption':
|
||||||
|
if (algorithm !== 'x25519-xsalsa20-poly1305') {
|
||||||
|
// prettier-ignore
|
||||||
|
cb(new Error(`Invalid key to be added, expected algorithm "x25519-xsalsa20-poly1305" for "${purpose}": ${JSON.stringify(addition)}`))
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// prettier-ignore
|
||||||
|
cb(new Error(`Invalid key to be added, expected purpose "sig", "shs-and-sig", or "external-encryption": ${JSON.stringify(addition)}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!promises.has(token)) {
|
if (!promises.has(token)) {
|
||||||
cb(new Error('Invalid token'))
|
cb(new Error('Invalid token'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const promise = /** @type {AccountAddPromise} */ (promises.get(token))
|
const promise = /** @type {AccountAddPromise} */ (promises.get(token))
|
||||||
const { type, account } = promise
|
const { type, account } = promise
|
||||||
if (type !== 'account-add') {
|
if (type !== 'account-add') {
|
||||||
|
@ -187,18 +241,17 @@ module.exports = {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const keypair = { curve: 'ed25519', public: addition.key.bytes }
|
const keypair = {
|
||||||
if (local.db.account.has({ account, keypair })) {
|
curve: /**@type {const}*/ ('ed25519'),
|
||||||
|
public: addition.key.bytes,
|
||||||
|
}
|
||||||
|
if (peer.db.account.has({ account, keypair })) {
|
||||||
cb(null, false)
|
cb(null, false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
local.db.account.add(
|
peer.db.account.add(
|
||||||
{ account, keypair, consent: addition.consent },
|
{ account, keypair, consent: addition.consent },
|
||||||
/**
|
|
||||||
* @param {Error | null} err
|
|
||||||
* @param {any} rec
|
|
||||||
*/
|
|
||||||
(err, rec) => {
|
(err, rec) => {
|
||||||
if (err) return cb(err)
|
if (err) return cb(err)
|
||||||
promises.delete(token)
|
promises.delete(token)
|
||||||
|
|
12
package.json
12
package.json
|
@ -14,14 +14,14 @@
|
||||||
"files": [
|
"files": [
|
||||||
"lib/**/*"
|
"lib/**/*"
|
||||||
],
|
],
|
||||||
"engines": {
|
|
||||||
"node": ">=16"
|
|
||||||
},
|
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"require": "./lib/index.js"
|
"require": "./lib/index.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"atomic-file-rw": "~0.3.0",
|
"atomic-file-rw": "~0.3.0",
|
||||||
"b4a": "^1.6.4",
|
"b4a": "^1.6.4",
|
||||||
|
@ -38,9 +38,9 @@
|
||||||
"prettier": "^2.6.2",
|
"prettier": "^2.6.2",
|
||||||
"pretty-quick": "^3.1.3",
|
"pretty-quick": "^3.1.3",
|
||||||
"rimraf": "^5.0.1",
|
"rimraf": "^5.0.1",
|
||||||
"secret-handshake-ext": "~0.0.8",
|
"secret-handshake-ext": "0.0.11",
|
||||||
"secret-stack": "~7.1.0",
|
"secret-stack": "~8.0.0",
|
||||||
"typescript": "^5.0.2"
|
"typescript": "^5.1.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean-check": "tsc --build --clean",
|
"clean-check": "tsc --build --clean",
|
||||||
|
|
|
@ -8,28 +8,33 @@ const rimraf = require('rimraf')
|
||||||
const Keypair = require('ppppp-keypair')
|
const Keypair = require('ppppp-keypair')
|
||||||
const caps = require('ppppp-caps')
|
const caps = require('ppppp-caps')
|
||||||
|
|
||||||
function setup() {
|
async function setup() {
|
||||||
setup.counter ??= 0
|
setup.counter ??= 0
|
||||||
setup.counter += 1
|
setup.counter += 1
|
||||||
const path = Path.join(os.tmpdir(), 'ppppp-promise-' + setup.counter)
|
const path = Path.join(os.tmpdir(), 'ppppp-promise-' + setup.counter)
|
||||||
rimraf.sync(path)
|
rimraf.sync(path)
|
||||||
const keypair = Keypair.generate('ed25519', 'alice')
|
const keypair = Keypair.generate('ed25519', 'alice')
|
||||||
|
|
||||||
const local = require('secret-stack/bare')({ caps })
|
const local = require('secret-stack/bare')()
|
||||||
.use(require('secret-stack/plugins/net'))
|
.use(require('secret-stack/plugins/net'))
|
||||||
.use(require('secret-handshake-ext/secret-stack'))
|
.use(require('secret-handshake-ext/secret-stack'))
|
||||||
.use(require('ppppp-db'))
|
.use(require('ppppp-db'))
|
||||||
.use(require('../lib'))
|
.use(require('../lib'))
|
||||||
.call(null, {
|
.call(null, {
|
||||||
path,
|
shse: { caps },
|
||||||
keypair,
|
global: {
|
||||||
|
path,
|
||||||
|
keypair,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await local.db.loaded()
|
||||||
|
|
||||||
return { local, path, keypair }
|
return { local, path, keypair }
|
||||||
}
|
}
|
||||||
|
|
||||||
test('create()', async (t) => {
|
test('create()', async (t) => {
|
||||||
const { local, path } = setup()
|
const { local, path } = await setup()
|
||||||
|
|
||||||
const promise = { type: 'follow' }
|
const promise = { type: 'follow' }
|
||||||
const token = await p(local.promise.create)(promise)
|
const token = await p(local.promise.create)(promise)
|
||||||
|
@ -45,9 +50,9 @@ test('create()', async (t) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test('follow()', async (t) => {
|
test('follow()', async (t) => {
|
||||||
const { local, path } = setup()
|
const { local, path } = await setup()
|
||||||
|
|
||||||
assert.rejects(() => p(local.promise.follow)('randomnottoken', 'MY_ID'))
|
assert.rejects(() => p(local.promise.follow)('randomnottoken', 'FRIEND_ID'))
|
||||||
|
|
||||||
const promise = { type: 'follow' }
|
const promise = { type: 'follow' }
|
||||||
const token = await p(local.promise.create)(promise)
|
const token = await p(local.promise.create)(promise)
|
||||||
|
@ -56,24 +61,24 @@ test('follow()', async (t) => {
|
||||||
const contentsBefore = fs.readFileSync(file, 'utf-8')
|
const contentsBefore = fs.readFileSync(file, 'utf-8')
|
||||||
assert.strictEqual(contentsBefore, JSON.stringify([[token, promise]]))
|
assert.strictEqual(contentsBefore, JSON.stringify([[token, promise]]))
|
||||||
|
|
||||||
const result1 = await p(local.promise.follow)(token, 'MY_ID')
|
const result1 = await p(local.promise.follow)(token, 'FRIEND_ID')
|
||||||
assert.strictEqual(result1, true)
|
assert.strictEqual(result1, true)
|
||||||
|
|
||||||
const contentsAfter = fs.readFileSync(file, 'utf-8')
|
const contentsAfter = fs.readFileSync(file, 'utf-8')
|
||||||
assert.strictEqual(contentsAfter, '[]')
|
assert.strictEqual(contentsAfter, '[]')
|
||||||
|
|
||||||
assert.rejects(() => p(local.promise.follow)(token, 'MY_ID'))
|
assert.rejects(() => p(local.promise.follow)(token, 'FRIEND_ID'))
|
||||||
|
|
||||||
await p(local.close)()
|
await p(local.close)()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('accountAdd()', async (t) => {
|
test('accountAdd()', async (t) => {
|
||||||
const { local, path, keypair } = setup()
|
const { local, path, keypair } = await setup()
|
||||||
|
|
||||||
assert.rejects(() => p(local.promise.accountAdd)('randomnottoken', {}))
|
assert.rejects(() => p(local.promise.accountAdd)('randomnottoken', {}))
|
||||||
|
|
||||||
const account = await p(local.db.account.findOrCreate)({
|
const account = await p(local.db.account.findOrCreate)({
|
||||||
domain: 'account',
|
subdomain: 'account',
|
||||||
})
|
})
|
||||||
|
|
||||||
const promise = { type: 'account-add', account }
|
const promise = { type: 'account-add', account }
|
||||||
|
@ -86,10 +91,10 @@ test('accountAdd()', async (t) => {
|
||||||
const dbBefore = [...local.db.msgs()].map(({ data }) => data)
|
const dbBefore = [...local.db.msgs()].map(({ data }) => data)
|
||||||
assert.equal(dbBefore.length, 1)
|
assert.equal(dbBefore.length, 1)
|
||||||
assert.equal(dbBefore[0].action, 'add')
|
assert.equal(dbBefore[0].action, 'add')
|
||||||
assert.equal(dbBefore[0].add.key.algorithm, 'ed25519')
|
assert.equal(dbBefore[0].key.algorithm, 'ed25519')
|
||||||
assert.equal(dbBefore[0].add.key.bytes, keypair.public)
|
assert.equal(dbBefore[0].key.bytes, keypair.public)
|
||||||
assert.equal(dbBefore[0].add.key.purpose, 'sig')
|
assert.equal(dbBefore[0].key.purpose, 'shs-and-sig')
|
||||||
assert(dbBefore[0].add.nonce)
|
assert(dbBefore[0].nonce)
|
||||||
|
|
||||||
const keypair2 = Keypair.generate('ed25519', 'bob')
|
const keypair2 = Keypair.generate('ed25519', 'bob')
|
||||||
const consent = local.db.account.consent({ account, keypair: keypair2 })
|
const consent = local.db.account.consent({ account, keypair: keypair2 })
|
||||||
|
@ -106,15 +111,15 @@ test('accountAdd()', async (t) => {
|
||||||
const dbAfter = [...local.db.msgs()].map(({ data }) => data)
|
const dbAfter = [...local.db.msgs()].map(({ data }) => data)
|
||||||
assert.equal(dbAfter.length, 2)
|
assert.equal(dbAfter.length, 2)
|
||||||
assert.equal(dbAfter[0].action, 'add')
|
assert.equal(dbAfter[0].action, 'add')
|
||||||
assert.equal(dbAfter[0].add.key.algorithm, 'ed25519')
|
assert.equal(dbAfter[0].key.algorithm, 'ed25519')
|
||||||
assert.equal(dbAfter[0].add.key.bytes, keypair.public)
|
assert.equal(dbAfter[0].key.bytes, keypair.public)
|
||||||
assert.equal(dbAfter[0].add.key.purpose, 'sig')
|
assert.equal(dbAfter[0].key.purpose, 'shs-and-sig')
|
||||||
assert(dbAfter[0].add.nonce)
|
assert(dbAfter[0].nonce)
|
||||||
assert.equal(dbAfter[1].action, 'add')
|
assert.equal(dbAfter[1].action, 'add')
|
||||||
assert.equal(dbAfter[1].add.key.algorithm, 'ed25519')
|
assert.equal(dbAfter[1].key.algorithm, 'ed25519')
|
||||||
assert.equal(dbAfter[1].add.key.bytes, keypair2.public)
|
assert.equal(dbAfter[1].key.bytes, keypair2.public)
|
||||||
assert.equal(dbAfter[1].add.key.purpose, 'sig')
|
assert.equal(dbAfter[1].key.purpose, 'sig')
|
||||||
assert(dbAfter[1].add.consent)
|
assert(dbAfter[1].consent)
|
||||||
|
|
||||||
const contentsAfter = fs.readFileSync(file, 'utf-8')
|
const contentsAfter = fs.readFileSync(file, 'utf-8')
|
||||||
assert.strictEqual(contentsAfter, '[]')
|
assert.strictEqual(contentsAfter, '[]')
|
||||||
|
@ -125,7 +130,7 @@ test('accountAdd()', async (t) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test('revoke()', async (t) => {
|
test('revoke()', async (t) => {
|
||||||
const { local, path } = setup()
|
const { local, path } = await setup()
|
||||||
|
|
||||||
const promise = { type: 'follow' }
|
const promise = { type: 'follow' }
|
||||||
const token = await p(local.promise.create)(promise)
|
const token = await p(local.promise.create)(promise)
|
||||||
|
|
Loading…
Reference in New Issue