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')
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @typedef {{type: 'account-add', account: string}} AccountAddPromise
|
||||
* @typedef {FollowPromise | AccountAddPromise} PPromise
|
||||
* @param {Config} config
|
||||
* @returns {asserts config is ExpectedConfig}
|
||||
*/
|
||||
|
||||
const FILENAME = 'promises.json'
|
||||
function assertValidConfig(config) {
|
||||
if (typeof config.global?.path !== 'string') {
|
||||
throw new Error('promise plugin requires config.global.path')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
name: 'promise',
|
||||
|
@ -43,11 +57,13 @@ module.exports = {
|
|||
},
|
||||
|
||||
/**
|
||||
* @param {any} local
|
||||
* @param {any} config
|
||||
* @param {{ db: PPPPPDB | null }} peer
|
||||
* @param {Config} config
|
||||
*/
|
||||
init(local, config) {
|
||||
const devicePromisesFile = Path.join(config.path, FILENAME)
|
||||
init(peer, config) {
|
||||
assertDBPlugin(peer)
|
||||
assertValidConfig(config)
|
||||
const devicePromisesFile = Path.join(config.global.path, 'promises.json')
|
||||
|
||||
const promises = /** @type {Map<string, PPromise>} */ (new Map())
|
||||
let loaded = false
|
||||
|
@ -164,22 +180,60 @@ module.exports = {
|
|||
setTimeout(() => accountAdd(token, addition, cb), 100)
|
||||
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 (
|
||||
!addition?.key?.purpose ||
|
||||
!addition?.key?.algorithm ||
|
||||
!addition?.key?.bytes ||
|
||||
!addition?.consent ||
|
||||
addition?.key?.purpose !== 'sig' ||
|
||||
addition?.key?.algorithm !== 'ed25519'
|
||||
!addition?.key?.bytes
|
||||
) {
|
||||
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
|
||||
}
|
||||
|
||||
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)) {
|
||||
cb(new Error('Invalid token'))
|
||||
return
|
||||
}
|
||||
|
||||
const promise = /** @type {AccountAddPromise} */ (promises.get(token))
|
||||
const { type, account } = promise
|
||||
if (type !== 'account-add') {
|
||||
|
@ -187,18 +241,17 @@ module.exports = {
|
|||
return
|
||||
}
|
||||
|
||||
const keypair = { curve: 'ed25519', public: addition.key.bytes }
|
||||
if (local.db.account.has({ account, keypair })) {
|
||||
const keypair = {
|
||||
curve: /**@type {const}*/ ('ed25519'),
|
||||
public: addition.key.bytes,
|
||||
}
|
||||
if (peer.db.account.has({ account, keypair })) {
|
||||
cb(null, false)
|
||||
return
|
||||
}
|
||||
|
||||
local.db.account.add(
|
||||
peer.db.account.add(
|
||||
{ account, keypair, consent: addition.consent },
|
||||
/**
|
||||
* @param {Error | null} err
|
||||
* @param {any} rec
|
||||
*/
|
||||
(err, rec) => {
|
||||
if (err) return cb(err)
|
||||
promises.delete(token)
|
||||
|
|
12
package.json
12
package.json
|
@ -14,14 +14,14 @@
|
|||
"files": [
|
||||
"lib/**/*"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"require": "./lib/index.js"
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"dependencies": {
|
||||
"atomic-file-rw": "~0.3.0",
|
||||
"b4a": "^1.6.4",
|
||||
|
@ -38,9 +38,9 @@
|
|||
"prettier": "^2.6.2",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"rimraf": "^5.0.1",
|
||||
"secret-handshake-ext": "~0.0.8",
|
||||
"secret-stack": "~7.1.0",
|
||||
"typescript": "^5.0.2"
|
||||
"secret-handshake-ext": "0.0.11",
|
||||
"secret-stack": "~8.0.0",
|
||||
"typescript": "^5.1.3"
|
||||
},
|
||||
"scripts": {
|
||||
"clean-check": "tsc --build --clean",
|
||||
|
|
|
@ -8,28 +8,33 @@ const rimraf = require('rimraf')
|
|||
const Keypair = require('ppppp-keypair')
|
||||
const caps = require('ppppp-caps')
|
||||
|
||||
function setup() {
|
||||
async function setup() {
|
||||
setup.counter ??= 0
|
||||
setup.counter += 1
|
||||
const path = Path.join(os.tmpdir(), 'ppppp-promise-' + setup.counter)
|
||||
rimraf.sync(path)
|
||||
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-handshake-ext/secret-stack'))
|
||||
.use(require('ppppp-db'))
|
||||
.use(require('../lib'))
|
||||
.call(null, {
|
||||
path,
|
||||
keypair,
|
||||
shse: { caps },
|
||||
global: {
|
||||
path,
|
||||
keypair,
|
||||
},
|
||||
})
|
||||
|
||||
await local.db.loaded()
|
||||
|
||||
return { local, path, keypair }
|
||||
}
|
||||
|
||||
test('create()', async (t) => {
|
||||
const { local, path } = setup()
|
||||
const { local, path } = await setup()
|
||||
|
||||
const promise = { type: 'follow' }
|
||||
const token = await p(local.promise.create)(promise)
|
||||
|
@ -45,9 +50,9 @@ test('create()', 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 token = await p(local.promise.create)(promise)
|
||||
|
@ -56,24 +61,24 @@ test('follow()', async (t) => {
|
|||
const contentsBefore = fs.readFileSync(file, 'utf-8')
|
||||
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)
|
||||
|
||||
const contentsAfter = fs.readFileSync(file, 'utf-8')
|
||||
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)()
|
||||
})
|
||||
|
||||
test('accountAdd()', async (t) => {
|
||||
const { local, path, keypair } = setup()
|
||||
const { local, path, keypair } = await setup()
|
||||
|
||||
assert.rejects(() => p(local.promise.accountAdd)('randomnottoken', {}))
|
||||
|
||||
const account = await p(local.db.account.findOrCreate)({
|
||||
domain: 'account',
|
||||
subdomain: 'account',
|
||||
})
|
||||
|
||||
const promise = { type: 'account-add', account }
|
||||
|
@ -86,10 +91,10 @@ test('accountAdd()', async (t) => {
|
|||
const dbBefore = [...local.db.msgs()].map(({ data }) => data)
|
||||
assert.equal(dbBefore.length, 1)
|
||||
assert.equal(dbBefore[0].action, 'add')
|
||||
assert.equal(dbBefore[0].add.key.algorithm, 'ed25519')
|
||||
assert.equal(dbBefore[0].add.key.bytes, keypair.public)
|
||||
assert.equal(dbBefore[0].add.key.purpose, 'sig')
|
||||
assert(dbBefore[0].add.nonce)
|
||||
assert.equal(dbBefore[0].key.algorithm, 'ed25519')
|
||||
assert.equal(dbBefore[0].key.bytes, keypair.public)
|
||||
assert.equal(dbBefore[0].key.purpose, 'shs-and-sig')
|
||||
assert(dbBefore[0].nonce)
|
||||
|
||||
const keypair2 = Keypair.generate('ed25519', 'bob')
|
||||
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)
|
||||
assert.equal(dbAfter.length, 2)
|
||||
assert.equal(dbAfter[0].action, 'add')
|
||||
assert.equal(dbAfter[0].add.key.algorithm, 'ed25519')
|
||||
assert.equal(dbAfter[0].add.key.bytes, keypair.public)
|
||||
assert.equal(dbAfter[0].add.key.purpose, 'sig')
|
||||
assert(dbAfter[0].add.nonce)
|
||||
assert.equal(dbAfter[0].key.algorithm, 'ed25519')
|
||||
assert.equal(dbAfter[0].key.bytes, keypair.public)
|
||||
assert.equal(dbAfter[0].key.purpose, 'shs-and-sig')
|
||||
assert(dbAfter[0].nonce)
|
||||
assert.equal(dbAfter[1].action, 'add')
|
||||
assert.equal(dbAfter[1].add.key.algorithm, 'ed25519')
|
||||
assert.equal(dbAfter[1].add.key.bytes, keypair2.public)
|
||||
assert.equal(dbAfter[1].add.key.purpose, 'sig')
|
||||
assert(dbAfter[1].add.consent)
|
||||
assert.equal(dbAfter[1].key.algorithm, 'ed25519')
|
||||
assert.equal(dbAfter[1].key.bytes, keypair2.public)
|
||||
assert.equal(dbAfter[1].key.purpose, 'sig')
|
||||
assert(dbAfter[1].consent)
|
||||
|
||||
const contentsAfter = fs.readFileSync(file, 'utf-8')
|
||||
assert.strictEqual(contentsAfter, '[]')
|
||||
|
@ -125,7 +130,7 @@ test('accountAdd()', async (t) => {
|
|||
})
|
||||
|
||||
test('revoke()', async (t) => {
|
||||
const { local, path } = setup()
|
||||
const { local, path } = await setup()
|
||||
|
||||
const promise = { type: 'follow' }
|
||||
const token = await p(local.promise.create)(promise)
|
||||
|
|
Loading…
Reference in New Issue