@violetix/baileys 0.5.5 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +1530 -2009
  2. package/WAProto/index.d.ts +14030 -14586
  3. package/WAProto/index.js +58426 -88874
  4. package/lib/Defaults/index.d.ts +2 -7
  5. package/lib/Defaults/index.js +5 -20
  6. package/lib/Signal/Group/sender-key-message.js +19 -1
  7. package/lib/Signal/libsignal.js +82 -29
  8. package/lib/Signal/xeddsa-fast.js +114 -0
  9. package/lib/Socket/Client/types.js +6 -8
  10. package/lib/Socket/Client/websocket.js +3 -38
  11. package/lib/Socket/business.d.ts +0 -3
  12. package/lib/Socket/chats.d.ts +0 -3
  13. package/lib/Socket/chats.js +18 -45
  14. package/lib/Socket/communities.d.ts +0 -3
  15. package/lib/Socket/graphql.js +3 -7
  16. package/lib/Socket/groups.d.ts +0 -3
  17. package/lib/Socket/groups.js +3 -8
  18. package/lib/Socket/index.d.ts +15 -17
  19. package/lib/Socket/messages-recv.d.ts +0 -3
  20. package/lib/Socket/messages-recv.js +43 -127
  21. package/lib/Socket/messages-send.d.ts +0 -4
  22. package/lib/Socket/messages-send.js +63 -191
  23. package/lib/Socket/newsletter.d.ts +0 -3
  24. package/lib/Socket/newsletter.js +1 -2
  25. package/lib/Socket/newsletter.js.map +1 -1
  26. package/lib/Socket/socket.d.ts +0 -9
  27. package/lib/Socket/socket.js +53 -341
  28. package/lib/Store/make-in-memory-store.js +30 -41
  29. package/lib/Types/Auth.d.ts +2 -60
  30. package/lib/Types/Bussines.d.ts +2 -9
  31. package/lib/Types/Call.d.ts +2 -22
  32. package/lib/Types/Chat.d.ts +1 -2
  33. package/lib/Types/Contact.d.ts +2 -11
  34. package/lib/Types/Events.d.ts +2 -41
  35. package/lib/Types/GroupMetadata.d.ts +2 -36
  36. package/lib/Types/Message.d.ts +1 -11
  37. package/lib/Types/Product.d.ts +2 -5
  38. package/lib/Types/Signal.d.ts +2 -10
  39. package/lib/Types/Socket.d.ts +2 -33
  40. package/lib/Types/USync.d.ts +2 -9
  41. package/lib/Types/index.d.ts +1 -3
  42. package/lib/Utils/auth-utils.d.ts +2 -3
  43. package/lib/Utils/auth-utils.js +53 -37
  44. package/lib/Utils/business.js +11 -25
  45. package/lib/Utils/chat-utils.js +4 -6
  46. package/lib/Utils/decode-wa-message.js +2 -2
  47. package/lib/Utils/event-buffer.js +12 -21
  48. package/lib/Utils/generics.d.ts +5 -5
  49. package/lib/Utils/generics.js +7 -25
  50. package/lib/Utils/history.d.ts +1 -2
  51. package/lib/Utils/history.js +83 -137
  52. package/lib/Utils/index.d.ts +0 -4
  53. package/lib/Utils/index.js +0 -4
  54. package/lib/Utils/listener-guard.js +0 -2
  55. package/lib/Utils/message-retry-manager.js +0 -20
  56. package/lib/Utils/messages-media.d.ts +8 -39
  57. package/lib/Utils/messages-media.js +89 -218
  58. package/lib/Utils/messages.d.ts +0 -1
  59. package/lib/Utils/messages.js +36 -122
  60. package/lib/Utils/process-message.d.ts +0 -18
  61. package/lib/Utils/process-message.js +47 -99
  62. package/lib/Utils/rich-message-utils.js +4 -3
  63. package/lib/Utils/use-multi-file-auth-state.d.ts +3 -5
  64. package/lib/Utils/use-multi-file-auth-state.js +6 -27
  65. package/lib/Utils/use-single-file-auth-state.d.ts +1 -1
  66. package/lib/Utils/use-single-file-auth-state.js +3 -12
  67. package/lib/WABinary/types.d.ts +2 -6
  68. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +3 -6
  69. package/lib/WAUSync/Protocols/USyncContactProtocol.js +2 -3
  70. package/lib/WAUSync/USyncQuery.d.ts +1 -1
  71. package/lib/WAUSync/USyncQuery.js +4 -7
  72. package/package.json +107 -123
  73. package/lib/Utils/a2ui.d.ts +0 -50
  74. package/lib/Utils/a2ui.js +0 -168
  75. package/lib/Utils/anchor-guard.d.ts +0 -71
  76. package/lib/Utils/anchor-guard.js +0 -404
  77. package/lib/Utils/connection-manager.d.ts +0 -44
  78. package/lib/Utils/connection-manager.js +0 -258
  79. package/lib/Utils/ignore-key.d.ts +0 -51
  80. package/lib/Utils/ignore-key.js +0 -176
@@ -1,258 +0,0 @@
1
- import { EventEmitter } from 'events';
2
- import { Agent } from 'https';
3
- import { DisconnectReason } from '../Types/index.js';
4
- /**
5
- * Supervises a fleet of sockets inside one process.
6
- *
7
- * The library builds and tears down one socket; it deliberately does not reconnect, because only
8
- * the host knows which auth state to reload. With hundreds of accounts that leaves three fleet-level
9
- * problems to whoever wires them up, and getting them wrong is what makes accounts drop:
10
- *
11
- * - every socket reconnecting the moment the server closes it, all at once, which hammers the
12
- * endpoint and gets throttled or banned (this is what `baseDelayMs` + jitter + stagger fix);
13
- * - a permanently dead session (401/403/411/440) being reconnected forever, which is the
14
- * documented path to a ban (this is what DEAD_REASONS stops);
15
- * - unbounded parallel connects, which exhausts FDs and TLS handshakes on boot.
16
- *
17
- * Events are deliberately never named 'error': EventEmitter throws on an unhandled 'error', and one
18
- * dead session must not take the process down.
19
- *
20
- * ponytail: supervision only. Every socket still shares one event loop, so this bounds reconnect
21
- * storms and socket leaks but not CPU-bound crypto; past a few hundred accounts, shard by process.
22
- */
23
- const DEAD_REASONS = new Set([
24
- DisconnectReason.loggedOut,
25
- DisconnectReason.forbidden,
26
- DisconnectReason.multideviceMismatch,
27
- DisconnectReason.badSession,
28
- DisconnectReason.connectionReplaced
29
- ]);
30
- export class ConnectionManager extends EventEmitter {
31
- #create;
32
- #sessions = new Map();
33
- #maxConcurrent;
34
- #active = 0;
35
- #waiters = [];
36
- #reconnect;
37
- #baseDelayMs;
38
- #maxDelayMs;
39
- #maxAttempts;
40
- #staggerMs;
41
- #logger;
42
- #started = false;
43
- constructor({ create, sessions = [], maxConcurrent = 8, reconnect = true, baseDelayMs = 2000, maxDelayMs = 60000, maxAttempts = Infinity, staggerMs = 250, logger = console }) {
44
- super();
45
- if (typeof create !== 'function') {
46
- throw new TypeError('ConnectionManager needs a create(session) function');
47
- }
48
- for (const session of sessions) {
49
- if (!session?.id) {
50
- throw new TypeError('every session needs an id');
51
- }
52
- if (this.#sessions.has(session.id)) {
53
- throw new TypeError(`duplicate session id: ${session.id}`);
54
- }
55
- this.#sessions.set(session.id, {
56
- id: session.id,
57
- session,
58
- sock: null,
59
- state: 'idle',
60
- attempts: 0,
61
- generation: 0,
62
- timer: null,
63
- stopped: false,
64
- lastError: null
65
- });
66
- }
67
- this.#create = create;
68
- this.#maxConcurrent = Math.max(1, maxConcurrent);
69
- this.#reconnect = reconnect;
70
- this.#baseDelayMs = baseDelayMs;
71
- this.#maxDelayMs = maxDelayMs;
72
- this.#maxAttempts = maxAttempts;
73
- this.#staggerMs = staggerMs;
74
- this.#logger = logger;
75
- }
76
- /** Connect every session, staggered so boot does not fire them all in one tick. */
77
- start() {
78
- if (this.#started) {
79
- return this.stats();
80
- }
81
- this.#started = true;
82
- let index = 0;
83
- for (const rec of this.#sessions.values()) {
84
- const delayMs = this.#staggerMs * index++;
85
- if (!delayMs) {
86
- void this.#connect(rec);
87
- }
88
- else {
89
- rec.timer = setTimeout(() => {
90
- rec.timer = null;
91
- void this.#connect(rec);
92
- }, delayMs);
93
- }
94
- }
95
- return this.stats();
96
- }
97
- get(id) {
98
- return this.#sessions.get(id)?.sock ?? null;
99
- }
100
- /** Stop one session for good. A close event that arrives afterwards is ignored. */
101
- stop(id) {
102
- const rec = this.#sessions.get(id);
103
- if (!rec) {
104
- return false;
105
- }
106
- this.#stop(rec);
107
- return true;
108
- }
109
- /** Stop everything and wait for each socket's teardown. */
110
- async close() {
111
- const pending = [];
112
- for (const rec of this.#sessions.values()) {
113
- const sock = this.#stop(rec);
114
- if (sock) {
115
- pending.push(Promise.resolve(sock.end?.(undefined)).catch(() => { }));
116
- }
117
- }
118
- await Promise.all(pending);
119
- this.#started = false;
120
- this.removeAllListeners();
121
- }
122
- stats() {
123
- const counts = { total: 0, idle: 0, connecting: 0, open: 0, reconnecting: 0, closed: 0, failed: 0 };
124
- for (const rec of this.#sessions.values()) {
125
- counts.total += 1;
126
- counts[rec.state] += 1;
127
- }
128
- counts.activeConnects = this.#active;
129
- counts.queuedConnects = this.#waiters.length;
130
- return counts;
131
- }
132
- #stop(rec) {
133
- rec.stopped = true;
134
- rec.generation += 1;
135
- if (rec.timer) {
136
- clearTimeout(rec.timer);
137
- rec.timer = null;
138
- }
139
- const sock = rec.sock;
140
- rec.sock = null;
141
- rec.state = 'closed';
142
- return sock;
143
- }
144
- async #connect(rec) {
145
- if (rec.stopped) {
146
- return;
147
- }
148
- if (rec.timer) {
149
- clearTimeout(rec.timer);
150
- rec.timer = null;
151
- }
152
- const generation = ++rec.generation;
153
- rec.state = 'connecting';
154
- await this.#acquire();
155
- let sock;
156
- try {
157
- sock = await this.#create(rec.session);
158
- }
159
- catch (err) {
160
- this.#release();
161
- rec.lastError = err;
162
- this.emit('session-error', { id: rec.id, error: err });
163
- this.#scheduleReconnect(rec, err);
164
- return;
165
- }
166
- this.#release();
167
- // stop() or a newer connect won the race while this one was being created
168
- if (rec.stopped || generation !== rec.generation) {
169
- void sock?.end?.(undefined);
170
- return;
171
- }
172
- rec.sock = sock;
173
- try {
174
- sock.ev.on('connection.update', update => this.#onUpdate(rec, sock, update));
175
- }
176
- catch (err) {
177
- rec.lastError = err;
178
- this.emit('session-error', { id: rec.id, error: err });
179
- this.#scheduleReconnect(rec, err);
180
- }
181
- }
182
- #onUpdate(rec, sock, update) {
183
- // a socket that was already replaced must not drive this session any more
184
- if (rec.sock !== sock) {
185
- return;
186
- }
187
- const { connection, lastDisconnect, qr } = update;
188
- if (qr) {
189
- this.emit('qr', { id: rec.id, qr });
190
- }
191
- if (connection === 'open') {
192
- rec.state = 'open';
193
- rec.attempts = 0;
194
- rec.lastError = null;
195
- this.emit('open', { id: rec.id, sock });
196
- }
197
- else if (connection === 'close') {
198
- const error = lastDisconnect?.error;
199
- rec.sock = null;
200
- rec.lastError = error;
201
- this.emit('close', { id: rec.id, error, statusCode: error?.output?.statusCode });
202
- this.#scheduleReconnect(rec, error);
203
- }
204
- }
205
- #scheduleReconnect(rec, error) {
206
- if (!this.#reconnect || rec.stopped) {
207
- rec.state = 'closed';
208
- return;
209
- }
210
- const statusCode = error?.output?.statusCode;
211
- if (DEAD_REASONS.has(statusCode) || rec.attempts >= this.#maxAttempts) {
212
- rec.state = 'failed';
213
- this.#logger?.warn?.({ id: rec.id, statusCode, attempts: rec.attempts }, 'session will not be reconnected');
214
- this.emit('dead', { id: rec.id, error, statusCode });
215
- return;
216
- }
217
- // the server asks for a restart on 515; backing off there only delays the reconnect
218
- const immediate = statusCode === DisconnectReason.restartRequired;
219
- const backoff = Math.min(this.#maxDelayMs, this.#baseDelayMs * 2 ** rec.attempts);
220
- const delayMs = immediate ? 0 : Math.round(backoff * (0.75 + Math.random() * 0.5));
221
- rec.attempts += 1;
222
- rec.state = 'reconnecting';
223
- this.emit('reconnect', { id: rec.id, attempt: rec.attempts, delayMs, statusCode });
224
- rec.timer = setTimeout(() => {
225
- rec.timer = null;
226
- void this.#connect(rec);
227
- }, delayMs);
228
- }
229
- #acquire() {
230
- if (this.#active < this.#maxConcurrent) {
231
- this.#active += 1;
232
- return Promise.resolve();
233
- }
234
- return new Promise(resolve => this.#waiters.push(resolve));
235
- }
236
- #release() {
237
- const next = this.#waiters.shift();
238
- if (next) {
239
- // hand the slot over without touching the counter
240
- next();
241
- }
242
- else {
243
- this.#active -= 1;
244
- }
245
- }
246
- }
247
- export const makeConnectionManager = (options) => new ConnectionManager(options);
248
- /**
249
- * One TLS agent shared by every socket in the fleet. `ws` opens one TCP connection per socket, so
250
- * this does not multiplex them, but it does keep the agent, its DNS cache and its free-socket pool
251
- * shared instead of one per account.
252
- */
253
- export const makeWssAgent = ({ maxSockets = 256, keepAlive = true, keepAliveMsecs = 30000 } = {}) => new Agent({
254
- keepAlive,
255
- keepAliveMsecs,
256
- maxSockets,
257
- maxFreeSockets: Math.min(32, maxSockets)
258
- });
@@ -1,51 +0,0 @@
1
- import type { BinaryNode } from '../WABinary/index.js';
2
- export declare const IGNORE_KEY_KINDS: readonly ['message', 'receipt', 'notification', 'presence', 'chatstate', 'call'];
3
- export type WaIgnoreKeyKind = (typeof IGNORE_KEY_KINDS)[number];
4
- export type WaIgnoreKeyContext = {
5
- /** Stanza tag. */
6
- kind: WaIgnoreKeyKind;
7
- /** Deviceless chat JID; userless `from` values are kept verbatim. */
8
- remoteJid: string | null;
9
- /** Resolved against the account's own PN and LID. */
10
- fromMe: boolean;
11
- /** Stanza id. */
12
- id: string | undefined;
13
- /** Author in groups / broadcasts; `null` otherwise. */
14
- participant: string | null;
15
- };
16
- export type WaIgnoreKey = {
17
- /** Chat JID; array entries OR. Alt `sender_pn` / `sender_lid` attrs are matched too. */
18
- remoteJid?: string | readonly string[];
19
- /** Whether the stanza was sent by this account. */
20
- fromMe?: boolean;
21
- /** Stanza id. */
22
- id?: string;
23
- /** Author in groups / broadcasts. Alt forms are matched too. */
24
- participant?: string;
25
- /** Restrict to specific tags. Default: all six. */
26
- only?: readonly WaIgnoreKeyKind[];
27
- };
28
- export type WaIgnoreKeyPredicate = (ctx: WaIgnoreKeyContext) => boolean;
29
- export type WaIgnoreKeyInput = WaIgnoreKey | WaIgnoreKeyPredicate;
30
- export type WaIgnoreKeyEntry = {
31
- remoteJid?: string[];
32
- participant?: string;
33
- fromMe?: boolean;
34
- id?: string;
35
- only?: readonly WaIgnoreKeyKind[];
36
- predicate?: WaIgnoreKeyPredicate;
37
- };
38
- export declare const isIgnorableKind: (tag: string) => boolean;
39
- export declare const normalizeIgnoreKey: (input: WaIgnoreKeyInput) => WaIgnoreKeyEntry;
40
- export declare const buildIgnoreContext: (node: BinaryNode, meId?: string, meLid?: string) => WaIgnoreKeyContext;
41
- export declare const matchesIgnoreKey: (entry: WaIgnoreKeyEntry, ctx: WaIgnoreKeyContext, node: BinaryNode) => boolean;
42
- export declare const ignoredStanzaNeedsAck: (tag: string) => boolean;
43
- export declare const makeIgnoreKeyRegistry: (deps: {
44
- getMeId: () => string | undefined;
45
- getMeLid: () => string | undefined;
46
- logger?: any;
47
- }) => {
48
- register: (input: WaIgnoreKeyInput) => () => void;
49
- shouldDrop: (node: BinaryNode) => boolean;
50
- readonly size: number;
51
- };
@@ -1,176 +0,0 @@
1
- import { areJidsSameUser, jidDecode, jidEncode } from '../WABinary/index.js';
2
- /**
3
- * Stanza tags a filter may drop. Stream control, `iq` replies and the auth-critical
4
- * `success` / `failure` tags are deliberately absent so the connection flow stays intact.
5
- */
6
- export const IGNORE_KEY_KINDS = ['message', 'receipt', 'notification', 'presence', 'chatstate', 'call'];
7
- const IGNORE_KEY_KIND_SET = new Set(IGNORE_KEY_KINDS);
8
- /**
9
- * Kinds the server re-delivers until acked. Dropping one without an ack makes the server
10
- * retry it forever, so the filter acks on the handler's behalf. Presence and chat-state are
11
- * fire-and-forget and must not be acked.
12
- */
13
- const ACKED_KINDS = new Set(['message', 'receipt', 'notification', 'call']);
14
- /**
15
- * A JID arrives in whichever namespace the sender used, and the alt attributes carry the other
16
- * form. Matching every one of these lets a filter written with a phone number also catch the
17
- * LID-addressed copy of the same stanza (and vice versa).
18
- */
19
- const REMOTE_JID_ATTRS = ['from', 'sender_pn', 'sender_lid'];
20
- const PARTICIPANT_ATTRS = ['participant', 'participant_pn', 'participant_lid'];
21
- const DESCRIPTOR_FIELDS = ['remoteJid', 'fromMe', 'id', 'participant'];
22
- /** Deviceless user-form JID. Userless values (e.g. `s.whatsapp.net`) are kept verbatim. */
23
- const stripDevice = (jid) => {
24
- if (!jid) {
25
- return null;
26
- }
27
- const decoded = jidDecode(jid);
28
- if (!decoded?.user) {
29
- return jid;
30
- }
31
- return jidEncode(decoded.user, decoded.server);
32
- };
33
- /**
34
- * Compares deviceless JIDs as whole strings rather than by user part. `areJidsSameUser` ignores
35
- * the server, so it reports `5551234@s.whatsapp.net` and `5551234@lid` as the same user — two
36
- * unrelated accounts that would then share a filter. Server equality is required here; the
37
- * PN/LID crossover is handled by the alt attributes instead.
38
- */
39
- const sameJid = (a, b) => {
40
- if (!a || !b) {
41
- return false;
42
- }
43
- return stripDevice(a) === stripDevice(b);
44
- };
45
- export const isIgnorableKind = (tag) => IGNORE_KEY_KIND_SET.has(tag);
46
- /** Validates a descriptor or predicate and freezes it into an internal entry. */
47
- export const normalizeIgnoreKey = (input) => {
48
- if (typeof input === 'function') {
49
- return { predicate: input };
50
- }
51
- if (!input || typeof input !== 'object' || Array.isArray(input)) {
52
- throw new Error('ignoreKey expects a descriptor object or a predicate function');
53
- }
54
- if (!DESCRIPTOR_FIELDS.some(field => input[field] !== undefined)) {
55
- throw new Error('ignoreKey descriptor needs at least one of remoteJid, fromMe, id, participant');
56
- }
57
- const entry = {};
58
- if (input.remoteJid !== undefined) {
59
- const jids = (Array.isArray(input.remoteJid) ? input.remoteJid : [input.remoteJid]).filter(Boolean);
60
- if (!jids.length) {
61
- throw new Error('ignoreKey remoteJid cannot be empty');
62
- }
63
- entry.remoteJid = jids;
64
- }
65
- if (input.participant !== undefined) {
66
- if (typeof input.participant !== 'string' || !input.participant) {
67
- throw new Error('ignoreKey participant must be a non-empty jid');
68
- }
69
- entry.participant = input.participant;
70
- }
71
- if (input.fromMe !== undefined) {
72
- entry.fromMe = !!input.fromMe;
73
- }
74
- if (input.id !== undefined) {
75
- if (typeof input.id !== 'string' || !input.id) {
76
- throw new Error('ignoreKey id must be a non-empty string');
77
- }
78
- entry.id = input.id;
79
- }
80
- if (input.only !== undefined) {
81
- const only = Array.isArray(input.only) ? input.only : [input.only];
82
- if (!only.length) {
83
- throw new Error('ignoreKey only cannot be empty');
84
- }
85
- const unknown = only.filter(kind => !IGNORE_KEY_KIND_SET.has(kind));
86
- if (unknown.length) {
87
- throw new Error(`ignoreKey only has unknown kinds: ${unknown.join(', ')}`);
88
- }
89
- entry.only = only;
90
- }
91
- return entry;
92
- };
93
- /**
94
- * Parses the wire node into the shape filters match on, so a predicate never has to walk a
95
- * BinaryNode. `remoteJid` and `participant` are device-stripped to line up with the
96
- * `event.key.remoteJid` a handler would see.
97
- */
98
- export const buildIgnoreContext = (node, meId, meLid) => {
99
- const attrs = node.attrs || {};
100
- const author = attrs.participant || attrs.participant_pn || attrs.participant_lid || attrs.from;
101
- const fromMe = !!author && ((!!meId && areJidsSameUser(author, meId)) || (!!meLid && areJidsSameUser(author, meLid)));
102
- return {
103
- kind: node.tag,
104
- // a 1:1 message sent from another of our devices arrives from us, with the chat in `recipient`
105
- remoteJid: stripDevice(fromMe && attrs.recipient ? attrs.recipient : attrs.from),
106
- fromMe,
107
- id: attrs.id,
108
- participant: stripDevice(attrs.participant || attrs.participant_pn || attrs.participant_lid)
109
- };
110
- };
111
- export const matchesIgnoreKey = (entry, ctx, node) => {
112
- if (entry.predicate) {
113
- return !!entry.predicate(ctx);
114
- }
115
- if (entry.only && !entry.only.includes(ctx.kind)) {
116
- return false;
117
- }
118
- if (entry.id !== undefined && entry.id !== ctx.id) {
119
- return false;
120
- }
121
- if (entry.fromMe !== undefined && entry.fromMe !== ctx.fromMe) {
122
- return false;
123
- }
124
- const attrs = node.attrs || {};
125
- const remoteAttrs = ctx.fromMe && attrs.recipient ? ['recipient', ...REMOTE_JID_ATTRS] : REMOTE_JID_ATTRS;
126
- if (entry.remoteJid && !entry.remoteJid.some(wanted => remoteAttrs.some(attr => sameJid(attrs[attr], wanted)))) {
127
- return false;
128
- }
129
- if (entry.participant && !PARTICIPANT_ATTRS.some(attr => sameJid(attrs[attr], entry.participant))) {
130
- return false;
131
- }
132
- return true;
133
- };
134
- export const ignoredStanzaNeedsAck = (tag) => ACKED_KINDS.has(tag);
135
- /**
136
- * Holds the registered filters and answers the one question the socket asks per stanza.
137
- *
138
- * `category='peer'` traffic from this account's own devices is never dropped: those stanzas
139
- * carry app-state key shares, history sync and PDO responses between the account's devices, so
140
- * the common `{ fromMe: true }` filter would otherwise swallow the app-state key share and leave
141
- * the collections blocked on a key they re-request on every sync. The exemption only applies
142
- * when the sender really is this account — resolved against both `meJid` and `meLid` — so a
143
- * foreign stanza cannot dodge a filter by stamping the attribute.
144
- */
145
- export const makeIgnoreKeyRegistry = ({ getMeId, getMeLid, logger }) => {
146
- const entries = new Set();
147
- const register = (input) => {
148
- const entry = normalizeIgnoreKey(input);
149
- entries.add(entry);
150
- return () => {
151
- entries.delete(entry);
152
- };
153
- };
154
- const shouldDrop = (node) => {
155
- if (!entries.size || !isIgnorableKind(node.tag)) {
156
- return false;
157
- }
158
- const ctx = buildIgnoreContext(node, getMeId(), getMeLid());
159
- if (ctx.fromMe && node.attrs?.category === 'peer') {
160
- return false;
161
- }
162
- for (const entry of entries) {
163
- try {
164
- if (matchesIgnoreKey(entry, ctx, node)) {
165
- return true;
166
- }
167
- }
168
- catch (err) {
169
- // A throwing predicate must not take the stanza with it; the stanza stays.
170
- logger?.error({ err, kind: ctx.kind, id: ctx.id }, 'ignoreKey filter threw, keeping stanza');
171
- }
172
- }
173
- return false;
174
- };
175
- return { register, shouldDrop, get size() { return entries.size; } };
176
- };