diff --git a/lib/index.js b/lib/index.js index 859b61e..8bdfbd8 100644 --- a/lib/index.js +++ b/lib/index.js @@ -7,11 +7,12 @@ const b4a = require('b4a') /** * @typedef {ReturnType} PPPPPDB + * @typedef {ReturnType} PPPPPSet * @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: 'follow', account: string}} FollowPromise * @typedef {{type: 'account-add', account: string}} AccountAddPromise * @typedef {FollowPromise | AccountAddPromise} PPromise */ @@ -30,6 +31,15 @@ function assertDBPlugin(peer) { if (!peer.db) throw new Error('promise plugin plugin requires ppppp-db plugin') } +/** + * @param {{ set: PPPPPSet | null }} peer + * @returns {asserts peer is { set: PPPPPSet }} + */ +function assertSetPlugin(peer) { + // prettier-ignore + if (!peer.set) throw new Error('promise plugin plugin requires ppppp-set plugin') +} + /** * @param {Config} config * @returns {asserts config is ExpectedConfig} @@ -57,7 +67,7 @@ module.exports = { }, /** - * @param {{ db: PPPPPDB | null }} peer + * @param {{ db: PPPPPDB | null; set: PPPPPSet | null }} peer * @param {Config} config */ init(peer, config) { @@ -98,19 +108,21 @@ module.exports = { * @return {Error | null} */ function validatePromise(promise) { - if ( - typeof promise !== 'object' || - typeof promise.type !== 'string' || - (promise.type !== 'follow' && promise.type !== 'account-add') - ) { + if (typeof promise !== 'object' || typeof promise.type !== 'string') { return Error('Invalid promise created: ' + JSON.stringify(promise)) } - if ( - promise.type === 'account-add' && - typeof promise.account !== 'string' - ) { - // prettier-ignore - return Error('Invalid account-add promise missing "account" field: ' + JSON.stringify(promise)) + switch (promise.type) { + case 'follow': + case 'account-add': + if (typeof promise.account !== 'string') { + // prettier-ignore + return Error('Invalid promise missing "account" field: ' + JSON.stringify(promise)) + } else { + break + } + default: + // prettier-ignore + return Error('Invalid promise type: ' + JSON.stringify(promise)) } return null } @@ -138,22 +150,28 @@ module.exports = { const token = bs58.encode(crypto.randomBytes(32)) promises.set(token, promise) save((err, _) => { - if (err) return cb(err) + // prettier-ignore + if (err) return cb(new Error('Failed to save promise file when creating new promise', { cause: err })) cb(null, token) }) } /** * @param {string} token - * @param {string} id + * @param {string} accountID * @param {CB} cb */ - function follow(token, id, cb) { + function follow(token, accountID, cb) { if (!loaded) { - setTimeout(() => follow(token, id, cb), 100) + setTimeout(() => follow(token, accountID, cb), 100) + return + } + try { + assertSetPlugin(peer) + } catch (err) { + cb(/**@type {Error}*/ (err)) return } - if (!promises.has(token)) { cb(new Error('Invalid token')) return @@ -163,10 +181,28 @@ module.exports = { cb(new Error('Invalid token')) return } - console.log('ppppp-promise mock follow') // FIXME: implement follow - promises.delete(token) - save(() => { - cb(null, true) + const myAccountID = promise.account + const theirAccountID = accountID + + peer.set.load(myAccountID, (err) => { + // prettier-ignore + if (err) return cb(new Error(`Failed to load ppppp-set with account "${myAccountID}" when executing follow promise`, { cause: err })) + if (peer.set.has('follow', theirAccountID)) { + promises.delete(token) + cb(null, false) + return + } else { + peer.set.add('follow', theirAccountID, (err, _) => { + // prettier-ignore + if (err) return cb(new Error(`Failed to follow account "${theirAccountID}" in ppppp-set from account "${myAccountID}" when executing follow promise`, { cause: err })) + promises.delete(token) + save((err, _) => { + // prettier-ignore + if (err) return cb(new Error('Failed to save promise file when executing follow promise', { cause: err })) + cb(null, true) + }) + }) + } }) } diff --git a/package.json b/package.json index 2f43c22..6d557fb 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "ppppp-caps": "github:staltz/ppppp-caps", "ppppp-db": "github:staltz/ppppp-db", "ppppp-keypair": "github:staltz/ppppp-keypair", + "ppppp-set": "github:staltz/ppppp-set", "prettier": "^2.6.2", "pretty-quick": "^3.1.3", "rimraf": "^5.0.1", diff --git a/test/index.test.js b/test/index.test.js index 45e459b..13f96eb 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -19,6 +19,7 @@ async function setup() { .use(require('secret-stack/plugins/net')) .use(require('secret-handshake-ext/secret-stack')) .use(require('ppppp-db')) + .use(require('ppppp-set')) .use(require('../lib')) .call(null, { shse: { caps }, @@ -36,7 +37,11 @@ async function setup() { test('create()', async (t) => { const { local, path } = await setup() - const promise = { type: 'follow' } + const account = await p(local.db.account.findOrCreate)({ + subdomain: 'account', + }) + + const promise = { type: 'follow', account } const token = await p(local.promise.create)(promise) assert.strictEqual(typeof token, 'string') assert.ok(token.length > 42) @@ -54,16 +59,25 @@ test('follow()', async (t) => { assert.rejects(() => p(local.promise.follow)('randomnottoken', 'FRIEND_ID')) - const promise = { type: 'follow' } + const account = await p(local.db.account.findOrCreate)({ + subdomain: 'account', + }) + await p(local.set.load)(account) + + const promise = { type: 'follow', account } const token = await p(local.promise.create)(promise) const file = Path.join(path, 'promises.json') const contentsBefore = fs.readFileSync(file, 'utf-8') assert.strictEqual(contentsBefore, JSON.stringify([[token, promise]])) + assert.equal(local.set.has('follow', 'FRIEND_ID'), false, 'not following') + const result1 = await p(local.promise.follow)(token, 'FRIEND_ID') assert.strictEqual(result1, true) + assert.equal(local.set.has('follow', 'FRIEND_ID'), true, 'following') + const contentsAfter = fs.readFileSync(file, 'utf-8') assert.strictEqual(contentsAfter, '[]') @@ -132,7 +146,11 @@ test('accountAdd()', async (t) => { test('revoke()', async (t) => { const { local, path } = await setup() - const promise = { type: 'follow' } + const account = await p(local.db.account.findOrCreate)({ + subdomain: 'account', + }) + + const promise = { type: 'follow', account } const token = await p(local.promise.create)(promise) const file = Path.join(path, 'promises.json') diff --git a/tsconfig.json b/tsconfig.json index f6eb639..156adc5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,10 +6,10 @@ "noEmit": true, "exactOptionalPropertyTypes": true, "forceConsistentCasingInFileNames": true, - "lib": ["es2021", "dom"], + "lib": ["es2022", "dom"], "module": "node16", "skipLibCheck": true, "strict": true, - "target": "es2021" + "target": "es2022" } } \ No newline at end of file