nexus-fca 2.1.5 → 2.1.6
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.
- package/CHANGELOG.md +16 -0
- package/index.d.ts +5 -0
- package/index.js +47 -3
- package/lib/health/HealthMetrics.js +24 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.1.6] - 2025-08-31 - Memory Guard & Queue Sweeping
|
|
4
|
+
### Added
|
|
5
|
+
- Central lightweight memory guard sweeps: group queue pruning (idle >30m, overflow trim) and pendingEdits TTL sweeper (every 4m).
|
|
6
|
+
- Health metrics extended: memoryGuardRuns, memoryGuardActions, groupQueueDroppedMessages, groupQueueExpiredQueues, groupQueuePrunedThreads, pendingEditSweeps.
|
|
7
|
+
- API: `api.getMemoryMetrics()` returns focused memory-related counters.
|
|
8
|
+
- Typings updated (`EditOptions`, new API methods) in `index.d.ts`.
|
|
9
|
+
|
|
10
|
+
### Improved
|
|
11
|
+
- Group queue now tracks `lastActive` and enforces idle purge + overflow protection with metrics.
|
|
12
|
+
- Pending edits TTL enforcement separated from resend watchdog for deterministic expiry.
|
|
13
|
+
|
|
14
|
+
### Notes
|
|
15
|
+
- All guards are low-frequency, low-impact; no change to delivery reliability or safety – only prevention of unbounded growth.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
3
19
|
## [2.1.5] - 2025-08-28 - PendingEdits & ACK Metrics
|
|
4
20
|
### Added
|
|
5
21
|
- PendingEdits buffer with cap (default 200) + TTL (5m) + resend attempts (2) + ACK timeout (12s).
|
package/index.d.ts
CHANGED
|
@@ -395,6 +395,11 @@ declare module 'nexus-fca' {
|
|
|
395
395
|
setMessageReaction: (reaction: string, messageID: string, callback?: (err?: Error) => void, forceCustomReaction?: boolean) => Promise<void>,
|
|
396
396
|
setMessageReactionMqtt: (reaction: string, messageID: string, threadID: string, callback?: (err?: Error) => void) => Promise<void>,
|
|
397
397
|
setOptions: (options: Partial<IFCAU_Options>) => void,
|
|
398
|
+
setEditOptions: (opts: EditOptions) => void,
|
|
399
|
+
setBackoffOptions: (opts: { base?: number; max?: number; factor?: number; jitter?: number }) => void,
|
|
400
|
+
enableLazyPreflight: (enable?: boolean) => void,
|
|
401
|
+
getHealthMetrics: () => any,
|
|
402
|
+
getMemoryMetrics: () => { pendingEdits: number; pendingEditsDropped: number; pendingEditsExpired: number; outboundQueueDepth: number; groupQueueDroppedMessages: number; memoryGuardRuns: number; memoryGuardActions: number } | null,
|
|
398
403
|
setTitle: (newTitle: string, threadID: string, callback?: (err?: Error) => void) => Promise<void>,
|
|
399
404
|
setTheme: (themeID?: string, threadID: string, callback?: (err?: Error) => void) => Promise<void>,
|
|
400
405
|
unsendMessage: (messageID: string, callback?: (err?: Error) => void) => Promise<void>,
|
package/index.js
CHANGED
|
@@ -273,7 +273,20 @@ function buildAPI(globalOptions, html, jar) {
|
|
|
273
273
|
getHealthMetrics: function(){ return ctx.health ? ctx.health.snapshot() : null; },
|
|
274
274
|
enableLazyPreflight(enable=true){ ctx.globalOptions.disablePreflight = !enable; },
|
|
275
275
|
setBackoffOptions(opts={}){ ctx.globalOptions.backoff = Object.assign(ctx.globalOptions.backoff||{}, opts); },
|
|
276
|
-
setEditOptions(opts={}){ Object.assign(ctx.globalOptions.editSettings, opts); }
|
|
276
|
+
setEditOptions(opts={}){ Object.assign(ctx.globalOptions.editSettings, opts); },
|
|
277
|
+
getMemoryMetrics(){
|
|
278
|
+
if(!ctx.health) return null;
|
|
279
|
+
const snap = ctx.health.snapshot();
|
|
280
|
+
return {
|
|
281
|
+
pendingEdits: snap.pendingEdits,
|
|
282
|
+
pendingEditsDropped: snap.pendingEditsDropped,
|
|
283
|
+
pendingEditsExpired: snap.pendingEditsExpired,
|
|
284
|
+
outboundQueueDepth: snap.outboundQueueDepth,
|
|
285
|
+
groupQueueDroppedMessages: snap.groupQueueDroppedMessages,
|
|
286
|
+
memoryGuardRuns: snap.memoryGuardRuns,
|
|
287
|
+
memoryGuardActions: snap.memoryGuardActions
|
|
288
|
+
};
|
|
289
|
+
}
|
|
277
290
|
};
|
|
278
291
|
const defaultFuncs = utils.makeDefaults(html, i_userID || userID, ctx);
|
|
279
292
|
require("fs")
|
|
@@ -315,7 +328,7 @@ function buildAPI(globalOptions, html, jar) {
|
|
|
315
328
|
}, 1000 * 60 * 60 * 24);
|
|
316
329
|
// === Group Queue (No Cooldown, Sequential per group) ===
|
|
317
330
|
(function initGroupQueue(){
|
|
318
|
-
const groupQueues = new Map(); // threadID -> { q: [], sending: false }
|
|
331
|
+
const groupQueues = new Map(); // threadID -> { q: [], sending: false, lastActive: number }
|
|
319
332
|
const isGroupThread = (tid) => typeof tid === 'string' && tid.length >= 15; // simple heuristic
|
|
320
333
|
const DIRECT_FN = api.sendMessage; // original
|
|
321
334
|
|
|
@@ -325,6 +338,8 @@ function buildAPI(globalOptions, html, jar) {
|
|
|
325
338
|
api.setGroupQueueCapacity = function(n){ globalOptions.groupQueueMax = n; };
|
|
326
339
|
api.enableGroupQueue(true);
|
|
327
340
|
api.setGroupQueueCapacity(100); // allow up to 100 pending per group
|
|
341
|
+
// New: group queue retention policy
|
|
342
|
+
globalOptions.groupQueueIdleMs = 30*60*1000; // 30m idle purge
|
|
328
343
|
|
|
329
344
|
api._sendMessageDirect = DIRECT_FN;
|
|
330
345
|
api.sendMessage = function(message, threadID, cb){
|
|
@@ -332,10 +347,12 @@ function buildAPI(globalOptions, html, jar) {
|
|
|
332
347
|
return api._sendMessageDirect(message, threadID, cb);
|
|
333
348
|
}
|
|
334
349
|
let entry = groupQueues.get(threadID);
|
|
335
|
-
if(!entry){ entry = { q: [], sending: false }; groupQueues.set(threadID, entry); }
|
|
350
|
+
if(!entry){ entry = { q: [], sending: false, lastActive: Date.now() }; groupQueues.set(threadID, entry); }
|
|
351
|
+
entry.lastActive = Date.now();
|
|
336
352
|
if(entry.q.length >= (globalOptions.groupQueueMax||100)) {
|
|
337
353
|
// drop oldest (keep newest) to avoid unbounded growth
|
|
338
354
|
entry.q.shift();
|
|
355
|
+
if(ctx.health) ctx.health.recordGroupQueuePrune(0,0,1);
|
|
339
356
|
}
|
|
340
357
|
entry.q.push({ message, threadID, cb });
|
|
341
358
|
processQueue(threadID, entry);
|
|
@@ -364,6 +381,33 @@ function buildAPI(globalOptions, html, jar) {
|
|
|
364
381
|
}
|
|
365
382
|
entry.sending = false;
|
|
366
383
|
};
|
|
384
|
+
|
|
385
|
+
// Memory guard sweeper (lightweight)
|
|
386
|
+
if(!globalOptions._groupQueueSweeper){
|
|
387
|
+
globalOptions._groupQueueSweeper = setInterval(()=>{
|
|
388
|
+
const now = Date.now();
|
|
389
|
+
let prunedThreads = 0; let expiredQueues = 0; let dropped = 0; let actions = 0;
|
|
390
|
+
for(const [tid, entry] of groupQueues.entries()){
|
|
391
|
+
// Idle purge
|
|
392
|
+
if(now - entry.lastActive > (globalOptions.groupQueueIdleMs||1800000) && !entry.sending){
|
|
393
|
+
if(entry.q.length){ dropped += entry.q.length; }
|
|
394
|
+
groupQueues.delete(tid); expiredQueues++; actions++;
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
// Hard cap queue length (just in case capacity changed lower)
|
|
398
|
+
const cap = globalOptions.groupQueueMax||100;
|
|
399
|
+
if(entry.q.length > cap){
|
|
400
|
+
const overflow = entry.q.length - cap;
|
|
401
|
+
entry.q.splice(0, overflow); // drop oldest overflow
|
|
402
|
+
dropped += overflow; actions++;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if((prunedThreads||expiredQueues||dropped) && ctx.health){
|
|
406
|
+
ctx.health.recordGroupQueuePrune(prunedThreads, expiredQueues, dropped);
|
|
407
|
+
ctx.health.recordMemoryGuardRun(actions);
|
|
408
|
+
}
|
|
409
|
+
}, 5*60*1000); // every 5 minutes
|
|
410
|
+
}
|
|
367
411
|
})();
|
|
368
412
|
// === End Group Queue ===
|
|
369
413
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// Lightweight health & metrics tracker for Nexus-FCA (extended Stage 2)
|
|
2
|
+
// Lightweight health & metrics tracker for Nexus-FCA (extended Stage 2 + Memory Guard)
|
|
3
3
|
class HealthMetrics {
|
|
4
4
|
constructor() {
|
|
5
5
|
const now = Date.now();
|
|
@@ -26,6 +26,14 @@ class HealthMetrics {
|
|
|
26
26
|
this.p95AckLatencyMs = null;
|
|
27
27
|
this.editResends = 0;
|
|
28
28
|
this.editFailed = 0;
|
|
29
|
+
// Memory / queue guard metrics (Stage 3)
|
|
30
|
+
this.memoryGuardRuns = 0;
|
|
31
|
+
this.memoryGuardLastRun = 0;
|
|
32
|
+
this.memoryGuardActions = 0;
|
|
33
|
+
this.groupQueuePrunedThreads = 0;
|
|
34
|
+
this.groupQueueExpiredQueues = 0;
|
|
35
|
+
this.groupQueueDroppedMessages = 0;
|
|
36
|
+
this.pendingEditSweeps = 0;
|
|
29
37
|
}
|
|
30
38
|
onConnect() { this.lastConnectTs = Date.now(); this.consecutiveFailures = 0; }
|
|
31
39
|
onDisconnect() { this.lastDisconnectTs = Date.now(); }
|
|
@@ -64,9 +72,16 @@ class HealthMetrics {
|
|
|
64
72
|
for(const [mid, val] of this.pendingEditMap.entries()){
|
|
65
73
|
if(now - val.ts > ttlMs){ this.pendingEditMap.delete(mid); expired++; }
|
|
66
74
|
}
|
|
67
|
-
if(expired) { this.incPendingEditExpired(expired); }
|
|
75
|
+
if(expired) { this.incPendingEditExpired(expired); this.pendingEditSweeps++; }
|
|
68
76
|
this.pendingEdits = this.pendingEditMap.size;
|
|
69
77
|
}
|
|
78
|
+
// Memory guard helpers
|
|
79
|
+
recordMemoryGuardRun(actions=0){ this.memoryGuardRuns++; this.memoryGuardLastRun = Date.now(); this.memoryGuardActions += actions; }
|
|
80
|
+
recordGroupQueuePrune(threads, expiredQueues, droppedMsgs){
|
|
81
|
+
if(threads) this.groupQueuePrunedThreads += threads;
|
|
82
|
+
if(expiredQueues) this.groupQueueExpiredQueues += expiredQueues;
|
|
83
|
+
if(droppedMsgs) this.groupQueueDroppedMessages += droppedMsgs;
|
|
84
|
+
}
|
|
70
85
|
snapshot(){
|
|
71
86
|
const now = Date.now();
|
|
72
87
|
const idleMs = now - (this.lastMessageTs || this.lastConnectTs || now);
|
|
@@ -92,6 +107,13 @@ class HealthMetrics {
|
|
|
92
107
|
editFailed: this.editFailed,
|
|
93
108
|
outboundQueueDepth: this.outboundQueueDepth,
|
|
94
109
|
outboundQueueDropped: this.outboundQueueDropped,
|
|
110
|
+
memoryGuardRuns: this.memoryGuardRuns,
|
|
111
|
+
memoryGuardLastRun: this.memoryGuardLastRun,
|
|
112
|
+
memoryGuardActions: this.memoryGuardActions,
|
|
113
|
+
groupQueuePrunedThreads: this.groupQueuePrunedThreads,
|
|
114
|
+
groupQueueExpiredQueues: this.groupQueueExpiredQueues,
|
|
115
|
+
groupQueueDroppedMessages: this.groupQueueDroppedMessages,
|
|
116
|
+
pendingEditSweeps: this.pendingEditSweeps,
|
|
95
117
|
healthy: this.isHealthy(idleMs)
|
|
96
118
|
};
|
|
97
119
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexus-fca",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.6",
|
|
4
4
|
"description": "A modern, safe, and advanced Facebook Chat API for Node.js with fully integrated Nexus Login System. NPM-ready with ID/password/2FA support, ultra-low ban rate protection, and zero external dependencies.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|