summaryrefslogtreecommitdiffstats
path: root/tools/dm-filter-test.js
blob: b17bcd186dfe2f6a16d5746ec029385c3e97162e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// Who is allowed to buzz your phone. The wrap hides its sender from every
// server, so this decision is made on the device — and it has to be right in
// both directions: a friend must always get through, a stranger never, and
// anything we can't open must fall back to notifying rather than vanish.
// Run: bun tools/dm-filter-test.js
import { generateSecretKey, getPublicKey } from 'nostr-tools/pure';
import { bytesToHex } from '@noble/hashes/utils';
import { makeDM } from '../src/dm.js';
import { classifyDm, shouldNotifyDm } from '../src/dm-inbox.js';

let fails = 0;
const check = (name, ok, detail = '') => {
  console.log(` ${ok ? '✓' : '✗'} ${name}${detail ? ' — ' + detail : ''}`);
  if (!ok) fails++;
};

const meSk = generateSecretKey();
const me = getPublicKey(meSk);
const friendSk = generateSecretKey();
const friend = getPublicKey(friendSk);
const strangerSk = generateSecretKey();
const stranger = getPublicKey(strangerSk);

const inbox = { pubkey: me, sk: bytesToHex(meSk), follows: [friend], known: [], hasList: true, names: { [friend]: 'Alice' } };

console.log('[a friend]');
{
  const { toPeer } = await makeDM(friendSk, me, 'hey');
  const v = await classifyDm(toPeer, [inbox]);
  check('opens the wrap', !!v, v ? v.author.slice(0, 8) : 'null');
  check('names the sender', v && v.author === friend && v.name === 'Alice');
  check('is followed', v && v.followed);
  check('notifies', shouldNotifyDm(v));
}

console.log('\n[a stranger]');
{
  const { toPeer } = await makeDM(strangerSk, me, 'buy my coin');
  const v = await classifyDm(toPeer, [inbox]);
  check('opens the wrap', !!v);
  check('is not followed', v && !v.followed && v.author === stranger);
  check('stays silent', !shouldNotifyDm(v));
}

console.log('\n[our own sent copy]');
{
  const { toSelf } = await makeDM(meSk, friend, 'on my way');
  const v = await classifyDm(toSelf, [inbox]);
  check('recognises it as ours', v && v.mine);
  check('stays silent', !shouldNotifyDm(v));
}

console.log('\n[not ours to open]');
{
  // addressed to someone else entirely
  const { toPeer } = await makeDM(strangerSk, friend, 'psst');
  const v = await classifyDm(toPeer, [inbox]);
  check('cannot open it', v === null);
  check('falls back to notifying rather than dropping', shouldNotifyDm(v));
}

console.log('\n[a remote signer holds the key]');
{
  const { toPeer } = await makeDM(friendSk, me, 'hey');
  const v = await classifyDm(toPeer, [{ pubkey: me, sk: null, follows: [friend], hasList: true }]);
  check('no key, no verdict', v === null);
  check('notifies anyway', shouldNotifyDm(v));
}

console.log('\n[several wallets on one device]');
{
  const otherSk = generateSecretKey();
  const other = { pubkey: getPublicKey(otherSk), sk: bytesToHex(otherSk), follows: [], hasList: true };
  const { toPeer } = await makeDM(friendSk, me, 'hey');
  const v = await classifyDm(toPeer, [other, inbox]);
  check('finds the wallet it was addressed to', v && v.author === friend && v.followed);
  check('notifies', shouldNotifyDm(v));
}

console.log('\n[someone we are already talking to]');
{
  const { toPeer } = await makeDM(strangerSk, me, 'as we were saying');
  const v = await classifyDm(toPeer, [{ ...inbox, known: [stranger] }]);
  check('an open thread counts as known', v && v.known && !v.followed);
  check('notifies', shouldNotifyDm(v));
}

console.log('\n[no contact list published]');
{
  // A brand new account follows nobody. Filtering on an empty list would
  // silence everything, including the welcome message.
  const { toPeer } = await makeDM(strangerSk, me, 'welcome!');
  const v = await classifyDm(toPeer, [{ ...inbox, follows: [], hasList: false }]);
  check('nothing to filter against', v && !v.followed && !v.hasList);
  check('notifies rather than silencing everything', shouldNotifyDm(v));
  const own = (await makeDM(meSk, friend, 'hi')).toSelf;
  check('but our own copy is still silent', !shouldNotifyDm(await classifyDm(own, [{ ...inbox, follows: [], hasList: false }])));
}

console.log(fails ? `\n${fails} failing` : '\nall passing');
process.exit(fails ? 1 : 0);