@lazyneoaz/metachat 4.0.7 → 4.0.8
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/dist/index.cjs +255 -8
- package/dist/index.d.mts +121 -1
- package/dist/index.d.ts +121 -1
- package/dist/index.mjs +251 -9
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -25142,13 +25142,15 @@ function getHeaders(url2, options, ctx, customHeader) {
|
|
|
25142
25142
|
headers["priority"] = "u=0, i";
|
|
25143
25143
|
}
|
|
25144
25144
|
if (isXhr) {
|
|
25145
|
-
|
|
25146
|
-
|
|
25147
|
-
|
|
25145
|
+
if (!isGraphQl) {
|
|
25146
|
+
headers["X-Requested-With"] = "XMLHttpRequest";
|
|
25147
|
+
headers["Pragma"] = "no-cache";
|
|
25148
|
+
headers["Cache-Control"] = "no-cache";
|
|
25149
|
+
}
|
|
25148
25150
|
headers["priority"] = "u=1, i";
|
|
25149
25151
|
}
|
|
25150
25152
|
if (isGraphQl) {
|
|
25151
|
-
headers["x-asbd-id"] =
|
|
25153
|
+
headers["x-asbd-id"] = String(ctx?.asbdId || DEFAULT_ASBD_ID);
|
|
25152
25154
|
const lsd = ctx?.lsd || ctx?.fb_lsd;
|
|
25153
25155
|
if (lsd) headers["x-fb-lsd"] = sanitizeHeaderValue2(lsd);
|
|
25154
25156
|
const friendlyName = customHeader?.["x-fb-friendly-name"] || customHeader?.["X-Fb-Friendly-Name"];
|
|
@@ -25219,10 +25221,11 @@ function getFingerprintParams(options) {
|
|
|
25219
25221
|
dpr: String(profile.devicePixelRatio)
|
|
25220
25222
|
};
|
|
25221
25223
|
}
|
|
25222
|
-
var meta;
|
|
25224
|
+
var DEFAULT_ASBD_ID, meta;
|
|
25223
25225
|
var init_headers = __esm({
|
|
25224
25226
|
"src/utils/headers.ts"() {
|
|
25225
25227
|
init_user_agents();
|
|
25228
|
+
DEFAULT_ASBD_ID = "359341";
|
|
25226
25229
|
meta = (prop2) => new RegExp(`<meta property="${prop2}" content="([^"]*)"`);
|
|
25227
25230
|
}
|
|
25228
25231
|
});
|
|
@@ -75560,10 +75563,13 @@ __export(utils_exports2, {
|
|
|
75560
75563
|
NUM_TO_MONTH: () => NUM_TO_MONTH,
|
|
75561
75564
|
NetworkError: () => NetworkError,
|
|
75562
75565
|
NotFoundError: () => NotFoundError,
|
|
75566
|
+
PING_GRACE_MS: () => PING_GRACE_MS,
|
|
75563
75567
|
PermissionError: () => PermissionError,
|
|
75564
75568
|
RateLimitError: () => RateLimitError,
|
|
75569
|
+
RealtimeHealth: () => RealtimeHealth,
|
|
75565
75570
|
RecoverySupervisor: () => RecoverySupervisor,
|
|
75566
75571
|
SESSION_COOKIE_NAMES: () => SESSION_COOKIE_NAMES,
|
|
75572
|
+
SILENT_WINDOW_MS: () => SILENT_WINDOW_MS,
|
|
75567
75573
|
TimeoutError: () => TimeoutError,
|
|
75568
75574
|
ValidationError: () => ValidationError,
|
|
75569
75575
|
WINDOW_MS: () => WINDOW_MS,
|
|
@@ -75588,6 +75594,7 @@ __export(utils_exports2, {
|
|
|
75588
75594
|
delay: () => delay2,
|
|
75589
75595
|
ensureCometTokens: () => ensureCometTokens,
|
|
75590
75596
|
error: () => error,
|
|
75597
|
+
extractAsbdId: () => extractAsbdId,
|
|
75591
75598
|
extractAttachmentIds: () => extractAttachmentIds,
|
|
75592
75599
|
extractCookies: () => extractCookies,
|
|
75593
75600
|
extractCookiesFromBody: () => extractCookiesFromBody,
|
|
@@ -75638,6 +75645,7 @@ __export(utils_exports2, {
|
|
|
75638
75645
|
invalidateCometTokens: () => invalidateCometTokens,
|
|
75639
75646
|
isAuthFailure: () => isAuthFailure,
|
|
75640
75647
|
isBreakerOpen: () => isBreakerOpen,
|
|
75648
|
+
isLoggedOutDocument: () => isLoggedOutDocument,
|
|
75641
75649
|
isReadableStream: () => isReadableStream2,
|
|
75642
75650
|
isStaleTokenError: () => isStaleTokenError,
|
|
75643
75651
|
json: () => json2,
|
|
@@ -75675,6 +75683,7 @@ __export(utils_exports2, {
|
|
|
75675
75683
|
setProxy: () => setProxy2,
|
|
75676
75684
|
sleep: () => sleep,
|
|
75677
75685
|
startLiveness: () => startLiveness,
|
|
75686
|
+
startRealtimeSupervisor: () => startRealtimeSupervisor,
|
|
75678
75687
|
startRecovery: () => startRecovery,
|
|
75679
75688
|
success: () => success,
|
|
75680
75689
|
tripBreaker: () => tripBreaker,
|
|
@@ -91032,6 +91041,9 @@ function extractLsd(html3) {
|
|
|
91032
91041
|
function extractDtsg(html3) {
|
|
91033
91042
|
return getFrom3(html3, '["DTSGInitialData",[],{"token":"', '"}') || getFrom3(html3, 'name="fb_dtsg" value="', '"') || (html3.match(/"DTSGInitialData",\[\],\{"token":"([^"]+)"/) || [])[1] || "";
|
|
91034
91043
|
}
|
|
91044
|
+
function extractAsbdId(html3) {
|
|
91045
|
+
return (html3.match(/"asbd_id":(\d+)/) || [])[1] || (html3.match(/x-asbd-id["']?\s*[:=]\s*["']?(\d+)/i) || [])[1] || (html3.match(/"ASBD_ID":(\d+)/) || [])[1] || "";
|
|
91046
|
+
}
|
|
91035
91047
|
function extractSpin(html3) {
|
|
91036
91048
|
const out = {};
|
|
91037
91049
|
const spinB = (html3.match(/"__spin_b":"([^"]+)"/) || [])[1];
|
|
@@ -91098,6 +91110,8 @@ async function ensureCometTokens(ctx, defaultFuncs, force = false) {
|
|
|
91098
91110
|
const spin = extractSpin(html3);
|
|
91099
91111
|
if (Object.keys(spin).length) ctx.__spin = { ...ctx.__spin || {}, ...spin };
|
|
91100
91112
|
if (spin.__rev) ctx.revision = spin.__rev;
|
|
91113
|
+
const asbd = extractAsbdId(html3);
|
|
91114
|
+
if (asbd) ctx.asbdId = asbd;
|
|
91101
91115
|
ctx.lsd = lsd;
|
|
91102
91116
|
ctx.fb_dtsg = dtsg;
|
|
91103
91117
|
ctx.jazoest = recomputeJazoest(dtsg);
|
|
@@ -91368,6 +91382,13 @@ function isAuthFailure(value) {
|
|
|
91368
91382
|
}
|
|
91369
91383
|
return false;
|
|
91370
91384
|
}
|
|
91385
|
+
function isLoggedOutDocument(html3) {
|
|
91386
|
+
if (typeof html3 !== "string" || html3.length < 200) return false;
|
|
91387
|
+
if (!/<html/i.test(html3)) return false;
|
|
91388
|
+
const hasAuthedTokens = /DTSGInitialData|"LSD",\[\]/.test(html3);
|
|
91389
|
+
const looksLikeLogin = /name="(?:email|pass)"|id="email"|"login_form"|LoginForm|login_form/i.test(html3) || /Continue as |Use another profile|Create new account/i.test(html3) || /You must log in to continue|Please log in|log in to continue/i.test(html3);
|
|
91390
|
+
return looksLikeLogin && !hasAuthedTokens;
|
|
91391
|
+
}
|
|
91371
91392
|
async function withSessionRetry(ctx, defaultFuncs, operation, options = {}) {
|
|
91372
91393
|
const attempts = Math.max(1, options.attempts ?? 2);
|
|
91373
91394
|
let lastError;
|
|
@@ -91474,6 +91495,8 @@ async function warmUpSession(ctx, getter, options = {}) {
|
|
|
91474
91495
|
const spin = extractSpin(html3);
|
|
91475
91496
|
if (Object.keys(spin).length) ctx.__spin = { ...ctx.__spin || {}, ...spin };
|
|
91476
91497
|
if (spin.__rev) ctx.revision = spin.__rev;
|
|
91498
|
+
const asbd = extractAsbdId(html3);
|
|
91499
|
+
if (asbd) ctx.asbdId = asbd;
|
|
91477
91500
|
tokensSeeded = true;
|
|
91478
91501
|
}
|
|
91479
91502
|
} else if (url2.includes("/messages")) {
|
|
@@ -91673,7 +91696,15 @@ var RecoverySupervisor = class {
|
|
|
91673
91696
|
async runOnce() {
|
|
91674
91697
|
this.state.checks += 1;
|
|
91675
91698
|
this.emit("check", `health check #${this.state.checks}`);
|
|
91676
|
-
|
|
91699
|
+
let healthy = this.runtime.isAuthenticated();
|
|
91700
|
+
if (healthy && this.runtime.probeLiveness) {
|
|
91701
|
+
try {
|
|
91702
|
+
healthy = await this.runtime.probeLiveness();
|
|
91703
|
+
} catch {
|
|
91704
|
+
healthy = true;
|
|
91705
|
+
}
|
|
91706
|
+
}
|
|
91707
|
+
if (healthy) {
|
|
91677
91708
|
this.markHealthy("session authenticated");
|
|
91678
91709
|
return;
|
|
91679
91710
|
}
|
|
@@ -91772,6 +91803,131 @@ function startRecovery(ctx, runtime, options = {}) {
|
|
|
91772
91803
|
return supervisor;
|
|
91773
91804
|
}
|
|
91774
91805
|
|
|
91806
|
+
// src/utils/realtime-health.ts
|
|
91807
|
+
init_pacing();
|
|
91808
|
+
var SILENT_WINDOW_MS = 6 * 60 * 1e3;
|
|
91809
|
+
var PING_GRACE_MS = 45 * 1e3;
|
|
91810
|
+
var RealtimeHealth = class {
|
|
91811
|
+
constructor(now = () => Date.now()) {
|
|
91812
|
+
this.now = now;
|
|
91813
|
+
this.frames = 0;
|
|
91814
|
+
this.lastFrameAt = 0;
|
|
91815
|
+
this.connectedAt = 0;
|
|
91816
|
+
this.probes = 0;
|
|
91817
|
+
this.forcedReconnects = 0;
|
|
91818
|
+
this.lastPingAt = 0;
|
|
91819
|
+
this.startedAt = Date.now();
|
|
91820
|
+
this.startedAt = this.now();
|
|
91821
|
+
}
|
|
91822
|
+
/** Record any inbound frame (message, pong, anything from the broker). */
|
|
91823
|
+
noteFrame() {
|
|
91824
|
+
this.frames += 1;
|
|
91825
|
+
this.lastFrameAt = this.now();
|
|
91826
|
+
}
|
|
91827
|
+
/** Record a successful connect. */
|
|
91828
|
+
noteConnected() {
|
|
91829
|
+
this.connectedAt = this.now();
|
|
91830
|
+
this.lastFrameAt = this.now();
|
|
91831
|
+
}
|
|
91832
|
+
/** Record that the transport reports itself disconnected. */
|
|
91833
|
+
noteDisconnected() {
|
|
91834
|
+
this.lastFrameAt = 0;
|
|
91835
|
+
}
|
|
91836
|
+
/** Record an active ping being sent. */
|
|
91837
|
+
notePing() {
|
|
91838
|
+
this.lastPingAt = this.now();
|
|
91839
|
+
}
|
|
91840
|
+
sinceLastFrame() {
|
|
91841
|
+
const base = this.lastFrameAt || this.connectedAt || this.startedAt;
|
|
91842
|
+
return this.now() - base;
|
|
91843
|
+
}
|
|
91844
|
+
/**
|
|
91845
|
+
* Decide what to do about the current socket.
|
|
91846
|
+
*
|
|
91847
|
+
* - `disconnected` — the transport already knows it is down; reconnect.
|
|
91848
|
+
* - `dead` — connected on paper, silent past the window, and a ping raised no
|
|
91849
|
+
* traffic within the grace period; reconnect.
|
|
91850
|
+
* - `suspect` — silent past the window but a ping was only just sent; wait.
|
|
91851
|
+
* - `quiet` — silent but inside the window; normal.
|
|
91852
|
+
* - `healthy` — recent traffic.
|
|
91853
|
+
*/
|
|
91854
|
+
verdict(connected) {
|
|
91855
|
+
if (!connected) return "disconnected";
|
|
91856
|
+
const silent = this.sinceLastFrame();
|
|
91857
|
+
if (silent < SILENT_WINDOW_MS / 2) return "healthy";
|
|
91858
|
+
if (silent < SILENT_WINDOW_MS) return "quiet";
|
|
91859
|
+
if (this.lastPingAt && this.now() - this.lastPingAt < PING_GRACE_MS) return "suspect";
|
|
91860
|
+
return "dead";
|
|
91861
|
+
}
|
|
91862
|
+
/** A snapshot for diagnostics. */
|
|
91863
|
+
state(connected) {
|
|
91864
|
+
return {
|
|
91865
|
+
connected,
|
|
91866
|
+
sinceLastFrameMs: this.sinceLastFrame(),
|
|
91867
|
+
frames: this.frames,
|
|
91868
|
+
probes: this.probes,
|
|
91869
|
+
forcedReconnects: this.forcedReconnects,
|
|
91870
|
+
verdict: this.verdict(connected)
|
|
91871
|
+
};
|
|
91872
|
+
}
|
|
91873
|
+
noteProbe() {
|
|
91874
|
+
this.probes += 1;
|
|
91875
|
+
}
|
|
91876
|
+
noteForcedReconnect() {
|
|
91877
|
+
this.forcedReconnects += 1;
|
|
91878
|
+
}
|
|
91879
|
+
};
|
|
91880
|
+
function startRealtimeSupervisor(options) {
|
|
91881
|
+
const health = new RealtimeHealth();
|
|
91882
|
+
const interval = options.intervalMs ?? 3 * 60 * 1e3;
|
|
91883
|
+
let stopped = false;
|
|
91884
|
+
let timer = null;
|
|
91885
|
+
const evaluate = () => {
|
|
91886
|
+
const connected = options.isConnected();
|
|
91887
|
+
const verdict = health.verdict(connected);
|
|
91888
|
+
health.noteProbe();
|
|
91889
|
+
if (verdict === "dead") {
|
|
91890
|
+
const pingSent = options.ping ? options.ping() : false;
|
|
91891
|
+
if (pingSent) {
|
|
91892
|
+
health.notePing();
|
|
91893
|
+
} else {
|
|
91894
|
+
health.noteForcedReconnect();
|
|
91895
|
+
options.reconnect("realtime socket silent and unpinnable");
|
|
91896
|
+
}
|
|
91897
|
+
}
|
|
91898
|
+
const state = health.state(connected);
|
|
91899
|
+
try {
|
|
91900
|
+
options.onVerdict?.(state);
|
|
91901
|
+
} catch {
|
|
91902
|
+
}
|
|
91903
|
+
return state;
|
|
91904
|
+
};
|
|
91905
|
+
const schedule = () => {
|
|
91906
|
+
if (stopped) return;
|
|
91907
|
+
timer = setTimeout(
|
|
91908
|
+
() => {
|
|
91909
|
+
timer = null;
|
|
91910
|
+
evaluate();
|
|
91911
|
+
schedule();
|
|
91912
|
+
},
|
|
91913
|
+
Math.max(1e3, humanDelay(interval, interval * 0.3))
|
|
91914
|
+
);
|
|
91915
|
+
if (timer && typeof timer.unref === "function") timer.unref();
|
|
91916
|
+
};
|
|
91917
|
+
schedule();
|
|
91918
|
+
return {
|
|
91919
|
+
health,
|
|
91920
|
+
checkNow: evaluate,
|
|
91921
|
+
stop: () => {
|
|
91922
|
+
stopped = true;
|
|
91923
|
+
if (timer) {
|
|
91924
|
+
clearTimeout(timer);
|
|
91925
|
+
timer = null;
|
|
91926
|
+
}
|
|
91927
|
+
}
|
|
91928
|
+
};
|
|
91929
|
+
}
|
|
91930
|
+
|
|
91775
91931
|
// src/utils/index.ts
|
|
91776
91932
|
async function json2(url2, jar2, qs2, options, ctx, customHeader) {
|
|
91777
91933
|
try {
|
|
@@ -91989,6 +92145,7 @@ async function buildAPI(html3, jar2, netData, globalOptions, fbLinkFunc, errorRe
|
|
|
91989
92145
|
const lsd = lsdConfig && lsdConfig.token || extractLsd(html3) || void 0;
|
|
91990
92146
|
const spin = extractSpin(html3);
|
|
91991
92147
|
const revision = spin.__rev || getFrom(html3, 'revision":', ",").trim();
|
|
92148
|
+
const asbdId = extractAsbdId(html3) || void 0;
|
|
91992
92149
|
const clientIDData = findConfig("MqttWebDeviceID");
|
|
91993
92150
|
const clientID = clientIDData ? clientIDData.clientID : void 0;
|
|
91994
92151
|
const mqttConfigData = findConfig("MqttWebConfig");
|
|
@@ -92030,7 +92187,8 @@ async function buildAPI(html3, jar2, netData, globalOptions, fbLinkFunc, errorRe
|
|
|
92030
92187
|
...dtsgResult,
|
|
92031
92188
|
...lsd ? { lsd } : {},
|
|
92032
92189
|
...Object.keys(spin).length ? { __spin: spin } : {},
|
|
92033
|
-
...revision ? { revision } : {}
|
|
92190
|
+
...revision ? { revision } : {},
|
|
92191
|
+
...asbdId ? { asbdId } : {}
|
|
92034
92192
|
};
|
|
92035
92193
|
if (lsd && dtsg) {
|
|
92036
92194
|
ctx.__cometTokens = { lsd, fb_dtsg: dtsg, fetchedAt: Date.now(), failures: 0 };
|
|
@@ -100728,7 +100886,9 @@ var listenMqtt_default = (defaultFuncs, api, ctx) => {
|
|
|
100728
100886
|
attempts: 0,
|
|
100729
100887
|
delay: MQTT_CONFIG.INITIAL_RETRY_DELAY,
|
|
100730
100888
|
throttleDelay: MQTT_CONFIG.THROTTLE_DELAY,
|
|
100731
|
-
generation: 0
|
|
100889
|
+
generation: 0,
|
|
100890
|
+
health: null,
|
|
100891
|
+
healthSupervisor: null
|
|
100732
100892
|
};
|
|
100733
100893
|
const log2 = (...args) => log(...args);
|
|
100734
100894
|
function clearReconnectTimer() {
|
|
@@ -100742,6 +100902,13 @@ var listenMqtt_default = (defaultFuncs, api, ctx) => {
|
|
|
100742
100902
|
clearInterval(lifecycle.presenceInterval);
|
|
100743
100903
|
lifecycle.presenceInterval = null;
|
|
100744
100904
|
}
|
|
100905
|
+
if (lifecycle.healthSupervisor) {
|
|
100906
|
+
try {
|
|
100907
|
+
lifecycle.healthSupervisor.stop();
|
|
100908
|
+
} catch (_3) {
|
|
100909
|
+
}
|
|
100910
|
+
lifecycle.healthSupervisor = null;
|
|
100911
|
+
}
|
|
100745
100912
|
clearReconnectTimer();
|
|
100746
100913
|
}
|
|
100747
100914
|
function destroyClient() {
|
|
@@ -100869,6 +101036,8 @@ var listenMqtt_default = (defaultFuncs, api, ctx) => {
|
|
|
100869
101036
|
});
|
|
100870
101037
|
lifecycle.client = client2;
|
|
100871
101038
|
ctx.mqttClient = client2;
|
|
101039
|
+
const health = new RealtimeHealth();
|
|
101040
|
+
lifecycle.health = health;
|
|
100872
101041
|
let connected = false;
|
|
100873
101042
|
let closing = false;
|
|
100874
101043
|
let throttled = false;
|
|
@@ -100888,12 +101057,14 @@ var listenMqtt_default = (defaultFuncs, api, ctx) => {
|
|
|
100888
101057
|
if (generation !== lifecycle.generation) return;
|
|
100889
101058
|
connected = false;
|
|
100890
101059
|
ctx.mqttConnected = false;
|
|
101060
|
+
health.noteDisconnected();
|
|
100891
101061
|
});
|
|
100892
101062
|
client2.on("close", () => {
|
|
100893
101063
|
if (generation !== lifecycle.generation) return;
|
|
100894
101064
|
if (connected) log2("MQTT connection closed.");
|
|
100895
101065
|
connected = false;
|
|
100896
101066
|
ctx.mqttConnected = false;
|
|
101067
|
+
health.noteDisconnected();
|
|
100897
101068
|
if (closing) return;
|
|
100898
101069
|
closing = true;
|
|
100899
101070
|
if (authFailure) {
|
|
@@ -100911,6 +101082,7 @@ var listenMqtt_default = (defaultFuncs, api, ctx) => {
|
|
|
100911
101082
|
client2.on("connect", () => {
|
|
100912
101083
|
if (generation !== lifecycle.generation) return;
|
|
100913
101084
|
connected = true;
|
|
101085
|
+
health.noteConnected();
|
|
100914
101086
|
client2.subscribe(MQTT_TOPICS, (subErr) => {
|
|
100915
101087
|
if (subErr) {
|
|
100916
101088
|
error("Failed to subscribe to MQTT topics:", subErr);
|
|
@@ -100947,6 +101119,49 @@ var listenMqtt_default = (defaultFuncs, api, ctx) => {
|
|
|
100947
101119
|
ctx.mqttConnected = true;
|
|
100948
101120
|
ctx.lastMqttError = null;
|
|
100949
101121
|
resetBackoff();
|
|
101122
|
+
if (lifecycle.healthSupervisor) {
|
|
101123
|
+
try {
|
|
101124
|
+
lifecycle.healthSupervisor.stop();
|
|
101125
|
+
} catch (_3) {
|
|
101126
|
+
}
|
|
101127
|
+
}
|
|
101128
|
+
lifecycle.healthSupervisor = startRealtimeSupervisor({
|
|
101129
|
+
isConnected: () => generation === lifecycle.generation && client2.connected,
|
|
101130
|
+
reconnect: (reason) => {
|
|
101131
|
+
if (generation !== lifecycle.generation) return;
|
|
101132
|
+
if (lifecycle.stopped || ctx.loggedIn === false) return;
|
|
101133
|
+
log2(`MQTT ${reason}; recycling the socket`);
|
|
101134
|
+
try {
|
|
101135
|
+
client2.end(true);
|
|
101136
|
+
} catch (_3) {
|
|
101137
|
+
}
|
|
101138
|
+
const gen = generation;
|
|
101139
|
+
setTimeout(() => {
|
|
101140
|
+
if (gen !== lifecycle.generation) return;
|
|
101141
|
+
if (lifecycle.stopped || ctx.loggedIn === false) return;
|
|
101142
|
+
if (lifecycle.client && lifecycle.client.connected) return;
|
|
101143
|
+
if (lifecycle.reconnectTimer) return;
|
|
101144
|
+
scheduleReconnect("realtime socket recycled");
|
|
101145
|
+
}, 5e3).unref?.();
|
|
101146
|
+
},
|
|
101147
|
+
ping: () => {
|
|
101148
|
+
try {
|
|
101149
|
+
client2.ping();
|
|
101150
|
+
return true;
|
|
101151
|
+
} catch {
|
|
101152
|
+
return false;
|
|
101153
|
+
}
|
|
101154
|
+
},
|
|
101155
|
+
onVerdict: (state) => {
|
|
101156
|
+
if (state.verdict === "dead") {
|
|
101157
|
+
warn(
|
|
101158
|
+
"MQTT health",
|
|
101159
|
+
`silent for ${Math.round(state.sinceLastFrameMs / 1e3)}s; probing`
|
|
101160
|
+
);
|
|
101161
|
+
}
|
|
101162
|
+
}
|
|
101163
|
+
});
|
|
101164
|
+
ctx.__mqttHealth = () => lifecycle.health ? lifecycle.health.state(generation === lifecycle.generation && !!client2.connected) : null;
|
|
100950
101165
|
api.getBotInitialData().then((botData) => {
|
|
100951
101166
|
if (botData) {
|
|
100952
101167
|
log(`Logged in as: ${botData.name || "Facebook User"} (${botData.uid || ctx.userID})`);
|
|
@@ -100957,6 +101172,7 @@ var listenMqtt_default = (defaultFuncs, api, ctx) => {
|
|
|
100957
101172
|
});
|
|
100958
101173
|
client2.on("message", async (topic, message, _packet) => {
|
|
100959
101174
|
try {
|
|
101175
|
+
health.noteFrame();
|
|
100960
101176
|
ctx.lastMessageTime = Date.now();
|
|
100961
101177
|
ctx.messageCount = (ctx.messageCount || 0) + 1;
|
|
100962
101178
|
const jsonMessage = JSON.parse(message.toString());
|
|
@@ -101042,6 +101258,16 @@ var listenMqtt_default = (defaultFuncs, api, ctx) => {
|
|
|
101042
101258
|
this.stop();
|
|
101043
101259
|
if (typeof cb === "function") cb();
|
|
101044
101260
|
}
|
|
101261
|
+
/** Live connection-health snapshot, for diagnostics/alerting. */
|
|
101262
|
+
getHealth() {
|
|
101263
|
+
if (!lifecycle.health) return null;
|
|
101264
|
+
const connected = !lifecycle.stopped && !!lifecycle.client && !!lifecycle.client.connected;
|
|
101265
|
+
return lifecycle.health.state(connected);
|
|
101266
|
+
}
|
|
101267
|
+
/** Is this listener currently stopped? */
|
|
101268
|
+
isStopped() {
|
|
101269
|
+
return lifecycle.stopped;
|
|
101270
|
+
}
|
|
101045
101271
|
}
|
|
101046
101272
|
const msgEmitter = new MessageEmitter();
|
|
101047
101273
|
lifecycle.stopped = false;
|
|
@@ -101363,6 +101589,22 @@ async function loginHelper(credentials, globalOptions, callback, setOptionsFunc,
|
|
|
101363
101589
|
{
|
|
101364
101590
|
ctx,
|
|
101365
101591
|
isAuthenticated: () => hasAuthenticatedSession(ctx.jar),
|
|
101592
|
+
probeLiveness: async () => {
|
|
101593
|
+
try {
|
|
101594
|
+
const res = await defaultFuncs.get(
|
|
101595
|
+
"https://www.facebook.com/",
|
|
101596
|
+
ctx.jar,
|
|
101597
|
+
null,
|
|
101598
|
+
ctx.globalOptions,
|
|
101599
|
+
{ noRef: true }
|
|
101600
|
+
);
|
|
101601
|
+
const html3 = typeof res === "string" ? res : res && res.body;
|
|
101602
|
+
if (typeof html3 !== "string" || !html3) return true;
|
|
101603
|
+
return !isLoggedOutDocument(html3);
|
|
101604
|
+
} catch {
|
|
101605
|
+
return true;
|
|
101606
|
+
}
|
|
101607
|
+
},
|
|
101366
101608
|
refresh: () => repairSession(ctx, defaultFuncs),
|
|
101367
101609
|
canRelogin: Boolean(credentials.email && credentials.password),
|
|
101368
101610
|
relogin: async () => {
|
|
@@ -101591,7 +101833,10 @@ lodash/lodash.js:
|
|
|
101591
101833
|
*)
|
|
101592
101834
|
*/
|
|
101593
101835
|
|
|
101836
|
+
exports.PING_GRACE_MS = PING_GRACE_MS;
|
|
101837
|
+
exports.RealtimeHealth = RealtimeHealth;
|
|
101594
101838
|
exports.RecoverySupervisor = RecoverySupervisor;
|
|
101839
|
+
exports.SILENT_WINDOW_MS = SILENT_WINDOW_MS;
|
|
101595
101840
|
exports.acquireCookies = acquireCookies;
|
|
101596
101841
|
exports.applySetCookie = applySetCookie;
|
|
101597
101842
|
exports.awaitProactiveSlot = awaitProactiveSlot;
|
|
@@ -101612,6 +101857,7 @@ exports.humanDelay = humanDelay;
|
|
|
101612
101857
|
exports.invalidateCometTokens = invalidateCometTokens;
|
|
101613
101858
|
exports.isAuthFailure = isAuthFailure;
|
|
101614
101859
|
exports.isBreakerOpen = isBreakerOpen;
|
|
101860
|
+
exports.isLoggedOutDocument = isLoggedOutDocument;
|
|
101615
101861
|
exports.isStaleTokenError = isStaleTokenError;
|
|
101616
101862
|
exports.login = login;
|
|
101617
101863
|
exports.missingSessionCookies = missingSessionCookies;
|
|
@@ -101632,6 +101878,7 @@ exports.retryDelay = retryDelay;
|
|
|
101632
101878
|
exports.safetySnapshot = safetySnapshot;
|
|
101633
101879
|
exports.sleep = sleep;
|
|
101634
101880
|
exports.startLiveness = startLiveness;
|
|
101881
|
+
exports.startRealtimeSupervisor = startRealtimeSupervisor;
|
|
101635
101882
|
exports.startRecovery = startRecovery;
|
|
101636
101883
|
exports.tripBreaker = tripBreaker;
|
|
101637
101884
|
exports.warmUpSession = warmUpSession;
|
package/dist/index.d.mts
CHANGED
|
@@ -145,6 +145,8 @@ interface ApiContext$1 {
|
|
|
145
145
|
fb_dtsg?: string;
|
|
146
146
|
jazoest?: string;
|
|
147
147
|
lsd?: string;
|
|
148
|
+
/** App-scoped abuse id (`x-asbd-id`) scraped from the current document. */
|
|
149
|
+
asbdId?: string;
|
|
148
150
|
ttstamp?: string;
|
|
149
151
|
threadTypeCache?: Record<string, boolean>;
|
|
150
152
|
lastMessageTime?: number;
|
|
@@ -733,6 +735,16 @@ declare function refreshSessionCookies(ctx: any, defaultFuncs: any, options?: {
|
|
|
733
735
|
}>;
|
|
734
736
|
/** Detect an auth/document-token failure in a thrown or returned payload. */
|
|
735
737
|
declare function isAuthFailure(value: any): boolean;
|
|
738
|
+
/**
|
|
739
|
+
* Detect a logged-out document.
|
|
740
|
+
*
|
|
741
|
+
* When Facebook invalidates a session it does not always answer with an auth
|
|
742
|
+
* error code: it serves the login page, or (when the browser/device is still
|
|
743
|
+
* trusted) the one-tap "Continue as <name>" interstitial. Both look like a
|
|
744
|
+
* normal `200 text/html`, so a session can silently become useless. Recognising
|
|
745
|
+
* the document lets the caller re-authenticate instead of limping along.
|
|
746
|
+
*/
|
|
747
|
+
declare function isLoggedOutDocument(html: any): boolean;
|
|
736
748
|
/**
|
|
737
749
|
* Wrap a network operation with one repair-and-retry pass.
|
|
738
750
|
*
|
|
@@ -1007,6 +1019,13 @@ interface RecoveryRuntime {
|
|
|
1007
1019
|
ctx: any;
|
|
1008
1020
|
/** True when the jar still holds a usable session. Cheap, synchronous. */
|
|
1009
1021
|
isAuthenticated: () => boolean;
|
|
1022
|
+
/**
|
|
1023
|
+
* Active server-side liveness check. Distinct from {@link isAuthenticated}:
|
|
1024
|
+
* the jar can still look authenticated locally while Facebook has already
|
|
1025
|
+
* invalidated the session server-side (the "Continue as <name>" page). When
|
|
1026
|
+
* provided, the supervisor trusts this over the cheap local check.
|
|
1027
|
+
*/
|
|
1028
|
+
probeLiveness?: () => Promise<boolean>;
|
|
1010
1029
|
/** Refresh cookies/tokens in place; resolves to whether auth survived. */
|
|
1011
1030
|
refresh: () => Promise<boolean>;
|
|
1012
1031
|
/**
|
|
@@ -1084,6 +1103,107 @@ declare class RecoverySupervisor {
|
|
|
1084
1103
|
*/
|
|
1085
1104
|
declare function startRecovery(ctx: any, runtime: RecoveryRuntime, options?: RecoveryOptions): RecoverySupervisor;
|
|
1086
1105
|
|
|
1106
|
+
/**
|
|
1107
|
+
* MQTT connection health.
|
|
1108
|
+
*
|
|
1109
|
+
* A Messenger realtime socket fails in two ways:
|
|
1110
|
+
*
|
|
1111
|
+
* 1. **Loudly** — TCP drop, broker close, auth error. mqtt.js raises `close`
|
|
1112
|
+
* and the listener reconnects. Easy.
|
|
1113
|
+
* 2. **Silently** — the socket stays "connected" but Facebook has stopped
|
|
1114
|
+
* routing events to it (a half-open connection, a broker-side idle reap, a
|
|
1115
|
+
* revoked session). No error is emitted, `client.connected` stays `true`,
|
|
1116
|
+
* and from the bot's point of view it is online while receiving nothing.
|
|
1117
|
+
*
|
|
1118
|
+
* Case 2 is why bots are traditionally torn down and restarted on a timer:
|
|
1119
|
+
* there was no way to tell a healthy quiet socket from a dead one. This module
|
|
1120
|
+
* provides that signal, so the socket can stay up indefinitely and only ever be
|
|
1121
|
+
* recycled when it is *actually* broken.
|
|
1122
|
+
*
|
|
1123
|
+
* The detector is deliberately conservative — real accounts genuinely go quiet
|
|
1124
|
+
* for long stretches, so it only acts when the socket has been silent for a
|
|
1125
|
+
* long window AND an active ping fails to elicit any traffic.
|
|
1126
|
+
*/
|
|
1127
|
+
/** No traffic and no pong for this long => the socket is suspect. */
|
|
1128
|
+
declare const SILENT_WINDOW_MS: number;
|
|
1129
|
+
/** How long to wait for traffic after an active ping before declaring death. */
|
|
1130
|
+
declare const PING_GRACE_MS: number;
|
|
1131
|
+
interface RealtimeHealthState {
|
|
1132
|
+
/** Whether the transport currently reports itself connected. */
|
|
1133
|
+
connected: boolean;
|
|
1134
|
+
/** ms since the last inbound frame (or since connect when none seen yet). */
|
|
1135
|
+
sinceLastFrameMs: number;
|
|
1136
|
+
/** Total inbound frames observed. */
|
|
1137
|
+
frames: number;
|
|
1138
|
+
/** Completed health probes. */
|
|
1139
|
+
probes: number;
|
|
1140
|
+
/** Times the health check forced a reconnect. */
|
|
1141
|
+
forcedReconnects: number;
|
|
1142
|
+
/** The last determined verdict. */
|
|
1143
|
+
verdict: "healthy" | "quiet" | "suspect" | "dead" | "disconnected";
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Tracks liveness of one realtime connection. Instances are cheap and are
|
|
1147
|
+
* recreated per connection generation by the listener.
|
|
1148
|
+
*/
|
|
1149
|
+
declare class RealtimeHealth {
|
|
1150
|
+
private now;
|
|
1151
|
+
private frames;
|
|
1152
|
+
private lastFrameAt;
|
|
1153
|
+
private connectedAt;
|
|
1154
|
+
private probes;
|
|
1155
|
+
private forcedReconnects;
|
|
1156
|
+
private lastPingAt;
|
|
1157
|
+
private startedAt;
|
|
1158
|
+
constructor(now?: () => number);
|
|
1159
|
+
/** Record any inbound frame (message, pong, anything from the broker). */
|
|
1160
|
+
noteFrame(): void;
|
|
1161
|
+
/** Record a successful connect. */
|
|
1162
|
+
noteConnected(): void;
|
|
1163
|
+
/** Record that the transport reports itself disconnected. */
|
|
1164
|
+
noteDisconnected(): void;
|
|
1165
|
+
/** Record an active ping being sent. */
|
|
1166
|
+
notePing(): void;
|
|
1167
|
+
sinceLastFrame(): number;
|
|
1168
|
+
/**
|
|
1169
|
+
* Decide what to do about the current socket.
|
|
1170
|
+
*
|
|
1171
|
+
* - `disconnected` — the transport already knows it is down; reconnect.
|
|
1172
|
+
* - `dead` — connected on paper, silent past the window, and a ping raised no
|
|
1173
|
+
* traffic within the grace period; reconnect.
|
|
1174
|
+
* - `suspect` — silent past the window but a ping was only just sent; wait.
|
|
1175
|
+
* - `quiet` — silent but inside the window; normal.
|
|
1176
|
+
* - `healthy` — recent traffic.
|
|
1177
|
+
*/
|
|
1178
|
+
verdict(connected: boolean): RealtimeHealthState["verdict"];
|
|
1179
|
+
/** A snapshot for diagnostics. */
|
|
1180
|
+
state(connected: boolean): RealtimeHealthState;
|
|
1181
|
+
noteProbe(): void;
|
|
1182
|
+
noteForcedReconnect(): void;
|
|
1183
|
+
}
|
|
1184
|
+
interface RealtimeSupervisorOptions {
|
|
1185
|
+
/** How often to evaluate the socket. Jittered. Default 3 min. */
|
|
1186
|
+
intervalMs?: number;
|
|
1187
|
+
/** Ask the transport to reconnect (fresh socket) — must be idempotent. */
|
|
1188
|
+
reconnect: (reason: string) => void;
|
|
1189
|
+
/** Send an MQTT ping. Return false if it could not be sent. */
|
|
1190
|
+
ping?: () => boolean;
|
|
1191
|
+
/** Whether the transport currently reports connected. */
|
|
1192
|
+
isConnected: () => boolean;
|
|
1193
|
+
/** Observe verdict changes (logging). */
|
|
1194
|
+
onVerdict?: (state: RealtimeHealthState) => void;
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* A lightweight supervisor that keeps ONE realtime socket honest: it pings a
|
|
1198
|
+
* long-quiet socket and recycles only a genuinely dead one. It never restarts a
|
|
1199
|
+
* socket that is simply idle, which is what lets the connection live for days.
|
|
1200
|
+
*/
|
|
1201
|
+
declare function startRealtimeSupervisor(options: RealtimeSupervisorOptions): {
|
|
1202
|
+
health: RealtimeHealth;
|
|
1203
|
+
stop: () => void;
|
|
1204
|
+
checkNow: () => RealtimeHealthState;
|
|
1205
|
+
};
|
|
1206
|
+
|
|
1087
1207
|
/** Extract the `CurrentUserInitialData` object from a profile HTML document. */
|
|
1088
1208
|
declare function extractCurrentUser(html: any): Record<string, any> | null;
|
|
1089
1209
|
|
|
@@ -1125,4 +1245,4 @@ declare const _default: typeof login$1 & {
|
|
|
1125
1245
|
default: typeof login$1;
|
|
1126
1246
|
};
|
|
1127
1247
|
|
|
1128
|
-
export { type API, type AddedStickerPackInfo, type AiTheme, type AnyAttachment, type ApiContext, type ApiInternal, type Attachment, type AudioAttachment, type BrowserProfile, type Callback, type CommentMessage, type CommentResult, type Coordinates, type DefaultFuncs, type EmojiEvent, type Event, type FileAttachment, type GroupNameEvent, type ListenEvent, type LivenessHandle, type LivenessState, type LoginCredentials, type LoginOptions, type Mention, type Message, type MessageID, type MessageObject, type MessageReply, type MusicTag, type MusicTrack, type NicknameEvent, type PhotoAttachment, type Reaction, type RecoveryEvent, type RecoveryOptions, type RecoveryRuntime, type RecoveryState, RecoverySupervisor, type SearchMusicOptions, type SearchMusicResult, type ShareAttachment, type ShareResult, type StickerAttachment, type StickerInfo, type StickerPackInfo, type ThreadID, type ThreadInfo, type ThreadThemeEvent, type TypingIndicator, type UnsendMessageEvent, type UserID, type UserInfo, type VideoAttachment, type WarmUpResult, acquireCookies, applySetCookie, awaitProactiveSlot, canActProactively, collectSessionCookies, _default as default, defaultBrowserProfile, ensureCometTokens, extractCookies, extractCookiesFromBody, extractCookiesFromJar, extractCookiesFromSetCookie, extractCurrentUser, getFingerprintParams, getHeaders, getWebSocketHeaders, hasAuthenticatedSession, humanDelay, invalidateCometTokens, isAuthFailure, isBreakerOpen, isStaleTokenError, login$1 as login, missingSessionCookies, normalizeBrowserProfile, noteAction, noteAuthFailure, noteCheckpoint, noteRateLimit, paceRequest, publicTypes, randomUserAgent, refreshSessionCookies, repairSession, reserveRequest, resetBreaker, resolveProfile, retryDelay, safetySnapshot, sleep, startLiveness, startRecovery, tripBreaker, warmUpSession, withCometTokens, withSessionRetry };
|
|
1248
|
+
export { type API, type AddedStickerPackInfo, type AiTheme, type AnyAttachment, type ApiContext, type ApiInternal, type Attachment, type AudioAttachment, type BrowserProfile, type Callback, type CommentMessage, type CommentResult, type Coordinates, type DefaultFuncs, type EmojiEvent, type Event, type FileAttachment, type GroupNameEvent, type ListenEvent, type LivenessHandle, type LivenessState, type LoginCredentials, type LoginOptions, type Mention, type Message, type MessageID, type MessageObject, type MessageReply, type MusicTag, type MusicTrack, type NicknameEvent, PING_GRACE_MS, type PhotoAttachment, type Reaction, RealtimeHealth, type RealtimeHealthState, type RecoveryEvent, type RecoveryOptions, type RecoveryRuntime, type RecoveryState, RecoverySupervisor, SILENT_WINDOW_MS, type SearchMusicOptions, type SearchMusicResult, type ShareAttachment, type ShareResult, type StickerAttachment, type StickerInfo, type StickerPackInfo, type ThreadID, type ThreadInfo, type ThreadThemeEvent, type TypingIndicator, type UnsendMessageEvent, type UserID, type UserInfo, type VideoAttachment, type WarmUpResult, acquireCookies, applySetCookie, awaitProactiveSlot, canActProactively, collectSessionCookies, _default as default, defaultBrowserProfile, ensureCometTokens, extractCookies, extractCookiesFromBody, extractCookiesFromJar, extractCookiesFromSetCookie, extractCurrentUser, getFingerprintParams, getHeaders, getWebSocketHeaders, hasAuthenticatedSession, humanDelay, invalidateCometTokens, isAuthFailure, isBreakerOpen, isLoggedOutDocument, isStaleTokenError, login$1 as login, missingSessionCookies, normalizeBrowserProfile, noteAction, noteAuthFailure, noteCheckpoint, noteRateLimit, paceRequest, publicTypes, randomUserAgent, refreshSessionCookies, repairSession, reserveRequest, resetBreaker, resolveProfile, retryDelay, safetySnapshot, sleep, startLiveness, startRealtimeSupervisor, startRecovery, tripBreaker, warmUpSession, withCometTokens, withSessionRetry };
|
package/dist/index.d.ts
CHANGED
|
@@ -145,6 +145,8 @@ interface ApiContext$1 {
|
|
|
145
145
|
fb_dtsg?: string;
|
|
146
146
|
jazoest?: string;
|
|
147
147
|
lsd?: string;
|
|
148
|
+
/** App-scoped abuse id (`x-asbd-id`) scraped from the current document. */
|
|
149
|
+
asbdId?: string;
|
|
148
150
|
ttstamp?: string;
|
|
149
151
|
threadTypeCache?: Record<string, boolean>;
|
|
150
152
|
lastMessageTime?: number;
|
|
@@ -733,6 +735,16 @@ declare function refreshSessionCookies(ctx: any, defaultFuncs: any, options?: {
|
|
|
733
735
|
}>;
|
|
734
736
|
/** Detect an auth/document-token failure in a thrown or returned payload. */
|
|
735
737
|
declare function isAuthFailure(value: any): boolean;
|
|
738
|
+
/**
|
|
739
|
+
* Detect a logged-out document.
|
|
740
|
+
*
|
|
741
|
+
* When Facebook invalidates a session it does not always answer with an auth
|
|
742
|
+
* error code: it serves the login page, or (when the browser/device is still
|
|
743
|
+
* trusted) the one-tap "Continue as <name>" interstitial. Both look like a
|
|
744
|
+
* normal `200 text/html`, so a session can silently become useless. Recognising
|
|
745
|
+
* the document lets the caller re-authenticate instead of limping along.
|
|
746
|
+
*/
|
|
747
|
+
declare function isLoggedOutDocument(html: any): boolean;
|
|
736
748
|
/**
|
|
737
749
|
* Wrap a network operation with one repair-and-retry pass.
|
|
738
750
|
*
|
|
@@ -1007,6 +1019,13 @@ interface RecoveryRuntime {
|
|
|
1007
1019
|
ctx: any;
|
|
1008
1020
|
/** True when the jar still holds a usable session. Cheap, synchronous. */
|
|
1009
1021
|
isAuthenticated: () => boolean;
|
|
1022
|
+
/**
|
|
1023
|
+
* Active server-side liveness check. Distinct from {@link isAuthenticated}:
|
|
1024
|
+
* the jar can still look authenticated locally while Facebook has already
|
|
1025
|
+
* invalidated the session server-side (the "Continue as <name>" page). When
|
|
1026
|
+
* provided, the supervisor trusts this over the cheap local check.
|
|
1027
|
+
*/
|
|
1028
|
+
probeLiveness?: () => Promise<boolean>;
|
|
1010
1029
|
/** Refresh cookies/tokens in place; resolves to whether auth survived. */
|
|
1011
1030
|
refresh: () => Promise<boolean>;
|
|
1012
1031
|
/**
|
|
@@ -1084,6 +1103,107 @@ declare class RecoverySupervisor {
|
|
|
1084
1103
|
*/
|
|
1085
1104
|
declare function startRecovery(ctx: any, runtime: RecoveryRuntime, options?: RecoveryOptions): RecoverySupervisor;
|
|
1086
1105
|
|
|
1106
|
+
/**
|
|
1107
|
+
* MQTT connection health.
|
|
1108
|
+
*
|
|
1109
|
+
* A Messenger realtime socket fails in two ways:
|
|
1110
|
+
*
|
|
1111
|
+
* 1. **Loudly** — TCP drop, broker close, auth error. mqtt.js raises `close`
|
|
1112
|
+
* and the listener reconnects. Easy.
|
|
1113
|
+
* 2. **Silently** — the socket stays "connected" but Facebook has stopped
|
|
1114
|
+
* routing events to it (a half-open connection, a broker-side idle reap, a
|
|
1115
|
+
* revoked session). No error is emitted, `client.connected` stays `true`,
|
|
1116
|
+
* and from the bot's point of view it is online while receiving nothing.
|
|
1117
|
+
*
|
|
1118
|
+
* Case 2 is why bots are traditionally torn down and restarted on a timer:
|
|
1119
|
+
* there was no way to tell a healthy quiet socket from a dead one. This module
|
|
1120
|
+
* provides that signal, so the socket can stay up indefinitely and only ever be
|
|
1121
|
+
* recycled when it is *actually* broken.
|
|
1122
|
+
*
|
|
1123
|
+
* The detector is deliberately conservative — real accounts genuinely go quiet
|
|
1124
|
+
* for long stretches, so it only acts when the socket has been silent for a
|
|
1125
|
+
* long window AND an active ping fails to elicit any traffic.
|
|
1126
|
+
*/
|
|
1127
|
+
/** No traffic and no pong for this long => the socket is suspect. */
|
|
1128
|
+
declare const SILENT_WINDOW_MS: number;
|
|
1129
|
+
/** How long to wait for traffic after an active ping before declaring death. */
|
|
1130
|
+
declare const PING_GRACE_MS: number;
|
|
1131
|
+
interface RealtimeHealthState {
|
|
1132
|
+
/** Whether the transport currently reports itself connected. */
|
|
1133
|
+
connected: boolean;
|
|
1134
|
+
/** ms since the last inbound frame (or since connect when none seen yet). */
|
|
1135
|
+
sinceLastFrameMs: number;
|
|
1136
|
+
/** Total inbound frames observed. */
|
|
1137
|
+
frames: number;
|
|
1138
|
+
/** Completed health probes. */
|
|
1139
|
+
probes: number;
|
|
1140
|
+
/** Times the health check forced a reconnect. */
|
|
1141
|
+
forcedReconnects: number;
|
|
1142
|
+
/** The last determined verdict. */
|
|
1143
|
+
verdict: "healthy" | "quiet" | "suspect" | "dead" | "disconnected";
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Tracks liveness of one realtime connection. Instances are cheap and are
|
|
1147
|
+
* recreated per connection generation by the listener.
|
|
1148
|
+
*/
|
|
1149
|
+
declare class RealtimeHealth {
|
|
1150
|
+
private now;
|
|
1151
|
+
private frames;
|
|
1152
|
+
private lastFrameAt;
|
|
1153
|
+
private connectedAt;
|
|
1154
|
+
private probes;
|
|
1155
|
+
private forcedReconnects;
|
|
1156
|
+
private lastPingAt;
|
|
1157
|
+
private startedAt;
|
|
1158
|
+
constructor(now?: () => number);
|
|
1159
|
+
/** Record any inbound frame (message, pong, anything from the broker). */
|
|
1160
|
+
noteFrame(): void;
|
|
1161
|
+
/** Record a successful connect. */
|
|
1162
|
+
noteConnected(): void;
|
|
1163
|
+
/** Record that the transport reports itself disconnected. */
|
|
1164
|
+
noteDisconnected(): void;
|
|
1165
|
+
/** Record an active ping being sent. */
|
|
1166
|
+
notePing(): void;
|
|
1167
|
+
sinceLastFrame(): number;
|
|
1168
|
+
/**
|
|
1169
|
+
* Decide what to do about the current socket.
|
|
1170
|
+
*
|
|
1171
|
+
* - `disconnected` — the transport already knows it is down; reconnect.
|
|
1172
|
+
* - `dead` — connected on paper, silent past the window, and a ping raised no
|
|
1173
|
+
* traffic within the grace period; reconnect.
|
|
1174
|
+
* - `suspect` — silent past the window but a ping was only just sent; wait.
|
|
1175
|
+
* - `quiet` — silent but inside the window; normal.
|
|
1176
|
+
* - `healthy` — recent traffic.
|
|
1177
|
+
*/
|
|
1178
|
+
verdict(connected: boolean): RealtimeHealthState["verdict"];
|
|
1179
|
+
/** A snapshot for diagnostics. */
|
|
1180
|
+
state(connected: boolean): RealtimeHealthState;
|
|
1181
|
+
noteProbe(): void;
|
|
1182
|
+
noteForcedReconnect(): void;
|
|
1183
|
+
}
|
|
1184
|
+
interface RealtimeSupervisorOptions {
|
|
1185
|
+
/** How often to evaluate the socket. Jittered. Default 3 min. */
|
|
1186
|
+
intervalMs?: number;
|
|
1187
|
+
/** Ask the transport to reconnect (fresh socket) — must be idempotent. */
|
|
1188
|
+
reconnect: (reason: string) => void;
|
|
1189
|
+
/** Send an MQTT ping. Return false if it could not be sent. */
|
|
1190
|
+
ping?: () => boolean;
|
|
1191
|
+
/** Whether the transport currently reports connected. */
|
|
1192
|
+
isConnected: () => boolean;
|
|
1193
|
+
/** Observe verdict changes (logging). */
|
|
1194
|
+
onVerdict?: (state: RealtimeHealthState) => void;
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* A lightweight supervisor that keeps ONE realtime socket honest: it pings a
|
|
1198
|
+
* long-quiet socket and recycles only a genuinely dead one. It never restarts a
|
|
1199
|
+
* socket that is simply idle, which is what lets the connection live for days.
|
|
1200
|
+
*/
|
|
1201
|
+
declare function startRealtimeSupervisor(options: RealtimeSupervisorOptions): {
|
|
1202
|
+
health: RealtimeHealth;
|
|
1203
|
+
stop: () => void;
|
|
1204
|
+
checkNow: () => RealtimeHealthState;
|
|
1205
|
+
};
|
|
1206
|
+
|
|
1087
1207
|
/** Extract the `CurrentUserInitialData` object from a profile HTML document. */
|
|
1088
1208
|
declare function extractCurrentUser(html: any): Record<string, any> | null;
|
|
1089
1209
|
|
|
@@ -1125,4 +1245,4 @@ declare const _default: typeof login$1 & {
|
|
|
1125
1245
|
default: typeof login$1;
|
|
1126
1246
|
};
|
|
1127
1247
|
|
|
1128
|
-
export { type API, type AddedStickerPackInfo, type AiTheme, type AnyAttachment, type ApiContext, type ApiInternal, type Attachment, type AudioAttachment, type BrowserProfile, type Callback, type CommentMessage, type CommentResult, type Coordinates, type DefaultFuncs, type EmojiEvent, type Event, type FileAttachment, type GroupNameEvent, type ListenEvent, type LivenessHandle, type LivenessState, type LoginCredentials, type LoginOptions, type Mention, type Message, type MessageID, type MessageObject, type MessageReply, type MusicTag, type MusicTrack, type NicknameEvent, type PhotoAttachment, type Reaction, type RecoveryEvent, type RecoveryOptions, type RecoveryRuntime, type RecoveryState, RecoverySupervisor, type SearchMusicOptions, type SearchMusicResult, type ShareAttachment, type ShareResult, type StickerAttachment, type StickerInfo, type StickerPackInfo, type ThreadID, type ThreadInfo, type ThreadThemeEvent, type TypingIndicator, type UnsendMessageEvent, type UserID, type UserInfo, type VideoAttachment, type WarmUpResult, acquireCookies, applySetCookie, awaitProactiveSlot, canActProactively, collectSessionCookies, _default as default, defaultBrowserProfile, ensureCometTokens, extractCookies, extractCookiesFromBody, extractCookiesFromJar, extractCookiesFromSetCookie, extractCurrentUser, getFingerprintParams, getHeaders, getWebSocketHeaders, hasAuthenticatedSession, humanDelay, invalidateCometTokens, isAuthFailure, isBreakerOpen, isStaleTokenError, login$1 as login, missingSessionCookies, normalizeBrowserProfile, noteAction, noteAuthFailure, noteCheckpoint, noteRateLimit, paceRequest, publicTypes, randomUserAgent, refreshSessionCookies, repairSession, reserveRequest, resetBreaker, resolveProfile, retryDelay, safetySnapshot, sleep, startLiveness, startRecovery, tripBreaker, warmUpSession, withCometTokens, withSessionRetry };
|
|
1248
|
+
export { type API, type AddedStickerPackInfo, type AiTheme, type AnyAttachment, type ApiContext, type ApiInternal, type Attachment, type AudioAttachment, type BrowserProfile, type Callback, type CommentMessage, type CommentResult, type Coordinates, type DefaultFuncs, type EmojiEvent, type Event, type FileAttachment, type GroupNameEvent, type ListenEvent, type LivenessHandle, type LivenessState, type LoginCredentials, type LoginOptions, type Mention, type Message, type MessageID, type MessageObject, type MessageReply, type MusicTag, type MusicTrack, type NicknameEvent, PING_GRACE_MS, type PhotoAttachment, type Reaction, RealtimeHealth, type RealtimeHealthState, type RecoveryEvent, type RecoveryOptions, type RecoveryRuntime, type RecoveryState, RecoverySupervisor, SILENT_WINDOW_MS, type SearchMusicOptions, type SearchMusicResult, type ShareAttachment, type ShareResult, type StickerAttachment, type StickerInfo, type StickerPackInfo, type ThreadID, type ThreadInfo, type ThreadThemeEvent, type TypingIndicator, type UnsendMessageEvent, type UserID, type UserInfo, type VideoAttachment, type WarmUpResult, acquireCookies, applySetCookie, awaitProactiveSlot, canActProactively, collectSessionCookies, _default as default, defaultBrowserProfile, ensureCometTokens, extractCookies, extractCookiesFromBody, extractCookiesFromJar, extractCookiesFromSetCookie, extractCurrentUser, getFingerprintParams, getHeaders, getWebSocketHeaders, hasAuthenticatedSession, humanDelay, invalidateCometTokens, isAuthFailure, isBreakerOpen, isLoggedOutDocument, isStaleTokenError, login$1 as login, missingSessionCookies, normalizeBrowserProfile, noteAction, noteAuthFailure, noteCheckpoint, noteRateLimit, paceRequest, publicTypes, randomUserAgent, refreshSessionCookies, repairSession, reserveRequest, resetBreaker, resolveProfile, retryDelay, safetySnapshot, sleep, startLiveness, startRealtimeSupervisor, startRecovery, tripBreaker, warmUpSession, withCometTokens, withSessionRetry };
|
package/dist/index.mjs
CHANGED
|
@@ -25107,13 +25107,15 @@ function getHeaders(url2, options, ctx, customHeader) {
|
|
|
25107
25107
|
headers["priority"] = "u=0, i";
|
|
25108
25108
|
}
|
|
25109
25109
|
if (isXhr) {
|
|
25110
|
-
|
|
25111
|
-
|
|
25112
|
-
|
|
25110
|
+
if (!isGraphQl) {
|
|
25111
|
+
headers["X-Requested-With"] = "XMLHttpRequest";
|
|
25112
|
+
headers["Pragma"] = "no-cache";
|
|
25113
|
+
headers["Cache-Control"] = "no-cache";
|
|
25114
|
+
}
|
|
25113
25115
|
headers["priority"] = "u=1, i";
|
|
25114
25116
|
}
|
|
25115
25117
|
if (isGraphQl) {
|
|
25116
|
-
headers["x-asbd-id"] =
|
|
25118
|
+
headers["x-asbd-id"] = String(ctx?.asbdId || DEFAULT_ASBD_ID);
|
|
25117
25119
|
const lsd = ctx?.lsd || ctx?.fb_lsd;
|
|
25118
25120
|
if (lsd) headers["x-fb-lsd"] = sanitizeHeaderValue2(lsd);
|
|
25119
25121
|
const friendlyName = customHeader?.["x-fb-friendly-name"] || customHeader?.["X-Fb-Friendly-Name"];
|
|
@@ -25184,10 +25186,11 @@ function getFingerprintParams(options) {
|
|
|
25184
25186
|
dpr: String(profile.devicePixelRatio)
|
|
25185
25187
|
};
|
|
25186
25188
|
}
|
|
25187
|
-
var meta;
|
|
25189
|
+
var DEFAULT_ASBD_ID, meta;
|
|
25188
25190
|
var init_headers = __esm({
|
|
25189
25191
|
"src/utils/headers.ts"() {
|
|
25190
25192
|
init_user_agents();
|
|
25193
|
+
DEFAULT_ASBD_ID = "359341";
|
|
25191
25194
|
meta = (prop2) => new RegExp(`<meta property="${prop2}" content="([^"]*)"`);
|
|
25192
25195
|
}
|
|
25193
25196
|
});
|
|
@@ -75525,10 +75528,13 @@ __export(utils_exports2, {
|
|
|
75525
75528
|
NUM_TO_MONTH: () => NUM_TO_MONTH,
|
|
75526
75529
|
NetworkError: () => NetworkError,
|
|
75527
75530
|
NotFoundError: () => NotFoundError,
|
|
75531
|
+
PING_GRACE_MS: () => PING_GRACE_MS,
|
|
75528
75532
|
PermissionError: () => PermissionError,
|
|
75529
75533
|
RateLimitError: () => RateLimitError,
|
|
75534
|
+
RealtimeHealth: () => RealtimeHealth,
|
|
75530
75535
|
RecoverySupervisor: () => RecoverySupervisor,
|
|
75531
75536
|
SESSION_COOKIE_NAMES: () => SESSION_COOKIE_NAMES,
|
|
75537
|
+
SILENT_WINDOW_MS: () => SILENT_WINDOW_MS,
|
|
75532
75538
|
TimeoutError: () => TimeoutError,
|
|
75533
75539
|
ValidationError: () => ValidationError,
|
|
75534
75540
|
WINDOW_MS: () => WINDOW_MS,
|
|
@@ -75553,6 +75559,7 @@ __export(utils_exports2, {
|
|
|
75553
75559
|
delay: () => delay2,
|
|
75554
75560
|
ensureCometTokens: () => ensureCometTokens,
|
|
75555
75561
|
error: () => error,
|
|
75562
|
+
extractAsbdId: () => extractAsbdId,
|
|
75556
75563
|
extractAttachmentIds: () => extractAttachmentIds,
|
|
75557
75564
|
extractCookies: () => extractCookies,
|
|
75558
75565
|
extractCookiesFromBody: () => extractCookiesFromBody,
|
|
@@ -75603,6 +75610,7 @@ __export(utils_exports2, {
|
|
|
75603
75610
|
invalidateCometTokens: () => invalidateCometTokens,
|
|
75604
75611
|
isAuthFailure: () => isAuthFailure,
|
|
75605
75612
|
isBreakerOpen: () => isBreakerOpen,
|
|
75613
|
+
isLoggedOutDocument: () => isLoggedOutDocument,
|
|
75606
75614
|
isReadableStream: () => isReadableStream2,
|
|
75607
75615
|
isStaleTokenError: () => isStaleTokenError,
|
|
75608
75616
|
json: () => json2,
|
|
@@ -75640,6 +75648,7 @@ __export(utils_exports2, {
|
|
|
75640
75648
|
setProxy: () => setProxy2,
|
|
75641
75649
|
sleep: () => sleep,
|
|
75642
75650
|
startLiveness: () => startLiveness,
|
|
75651
|
+
startRealtimeSupervisor: () => startRealtimeSupervisor,
|
|
75643
75652
|
startRecovery: () => startRecovery,
|
|
75644
75653
|
success: () => success,
|
|
75645
75654
|
tripBreaker: () => tripBreaker,
|
|
@@ -90997,6 +91006,9 @@ function extractLsd(html3) {
|
|
|
90997
91006
|
function extractDtsg(html3) {
|
|
90998
91007
|
return getFrom3(html3, '["DTSGInitialData",[],{"token":"', '"}') || getFrom3(html3, 'name="fb_dtsg" value="', '"') || (html3.match(/"DTSGInitialData",\[\],\{"token":"([^"]+)"/) || [])[1] || "";
|
|
90999
91008
|
}
|
|
91009
|
+
function extractAsbdId(html3) {
|
|
91010
|
+
return (html3.match(/"asbd_id":(\d+)/) || [])[1] || (html3.match(/x-asbd-id["']?\s*[:=]\s*["']?(\d+)/i) || [])[1] || (html3.match(/"ASBD_ID":(\d+)/) || [])[1] || "";
|
|
91011
|
+
}
|
|
91000
91012
|
function extractSpin(html3) {
|
|
91001
91013
|
const out = {};
|
|
91002
91014
|
const spinB = (html3.match(/"__spin_b":"([^"]+)"/) || [])[1];
|
|
@@ -91063,6 +91075,8 @@ async function ensureCometTokens(ctx, defaultFuncs, force = false) {
|
|
|
91063
91075
|
const spin = extractSpin(html3);
|
|
91064
91076
|
if (Object.keys(spin).length) ctx.__spin = { ...ctx.__spin || {}, ...spin };
|
|
91065
91077
|
if (spin.__rev) ctx.revision = spin.__rev;
|
|
91078
|
+
const asbd = extractAsbdId(html3);
|
|
91079
|
+
if (asbd) ctx.asbdId = asbd;
|
|
91066
91080
|
ctx.lsd = lsd;
|
|
91067
91081
|
ctx.fb_dtsg = dtsg;
|
|
91068
91082
|
ctx.jazoest = recomputeJazoest(dtsg);
|
|
@@ -91333,6 +91347,13 @@ function isAuthFailure(value) {
|
|
|
91333
91347
|
}
|
|
91334
91348
|
return false;
|
|
91335
91349
|
}
|
|
91350
|
+
function isLoggedOutDocument(html3) {
|
|
91351
|
+
if (typeof html3 !== "string" || html3.length < 200) return false;
|
|
91352
|
+
if (!/<html/i.test(html3)) return false;
|
|
91353
|
+
const hasAuthedTokens = /DTSGInitialData|"LSD",\[\]/.test(html3);
|
|
91354
|
+
const looksLikeLogin = /name="(?:email|pass)"|id="email"|"login_form"|LoginForm|login_form/i.test(html3) || /Continue as |Use another profile|Create new account/i.test(html3) || /You must log in to continue|Please log in|log in to continue/i.test(html3);
|
|
91355
|
+
return looksLikeLogin && !hasAuthedTokens;
|
|
91356
|
+
}
|
|
91336
91357
|
async function withSessionRetry(ctx, defaultFuncs, operation, options = {}) {
|
|
91337
91358
|
const attempts = Math.max(1, options.attempts ?? 2);
|
|
91338
91359
|
let lastError;
|
|
@@ -91439,6 +91460,8 @@ async function warmUpSession(ctx, getter, options = {}) {
|
|
|
91439
91460
|
const spin = extractSpin(html3);
|
|
91440
91461
|
if (Object.keys(spin).length) ctx.__spin = { ...ctx.__spin || {}, ...spin };
|
|
91441
91462
|
if (spin.__rev) ctx.revision = spin.__rev;
|
|
91463
|
+
const asbd = extractAsbdId(html3);
|
|
91464
|
+
if (asbd) ctx.asbdId = asbd;
|
|
91442
91465
|
tokensSeeded = true;
|
|
91443
91466
|
}
|
|
91444
91467
|
} else if (url2.includes("/messages")) {
|
|
@@ -91638,7 +91661,15 @@ var RecoverySupervisor = class {
|
|
|
91638
91661
|
async runOnce() {
|
|
91639
91662
|
this.state.checks += 1;
|
|
91640
91663
|
this.emit("check", `health check #${this.state.checks}`);
|
|
91641
|
-
|
|
91664
|
+
let healthy = this.runtime.isAuthenticated();
|
|
91665
|
+
if (healthy && this.runtime.probeLiveness) {
|
|
91666
|
+
try {
|
|
91667
|
+
healthy = await this.runtime.probeLiveness();
|
|
91668
|
+
} catch {
|
|
91669
|
+
healthy = true;
|
|
91670
|
+
}
|
|
91671
|
+
}
|
|
91672
|
+
if (healthy) {
|
|
91642
91673
|
this.markHealthy("session authenticated");
|
|
91643
91674
|
return;
|
|
91644
91675
|
}
|
|
@@ -91737,6 +91768,131 @@ function startRecovery(ctx, runtime, options = {}) {
|
|
|
91737
91768
|
return supervisor;
|
|
91738
91769
|
}
|
|
91739
91770
|
|
|
91771
|
+
// src/utils/realtime-health.ts
|
|
91772
|
+
init_pacing();
|
|
91773
|
+
var SILENT_WINDOW_MS = 6 * 60 * 1e3;
|
|
91774
|
+
var PING_GRACE_MS = 45 * 1e3;
|
|
91775
|
+
var RealtimeHealth = class {
|
|
91776
|
+
constructor(now = () => Date.now()) {
|
|
91777
|
+
this.now = now;
|
|
91778
|
+
this.frames = 0;
|
|
91779
|
+
this.lastFrameAt = 0;
|
|
91780
|
+
this.connectedAt = 0;
|
|
91781
|
+
this.probes = 0;
|
|
91782
|
+
this.forcedReconnects = 0;
|
|
91783
|
+
this.lastPingAt = 0;
|
|
91784
|
+
this.startedAt = Date.now();
|
|
91785
|
+
this.startedAt = this.now();
|
|
91786
|
+
}
|
|
91787
|
+
/** Record any inbound frame (message, pong, anything from the broker). */
|
|
91788
|
+
noteFrame() {
|
|
91789
|
+
this.frames += 1;
|
|
91790
|
+
this.lastFrameAt = this.now();
|
|
91791
|
+
}
|
|
91792
|
+
/** Record a successful connect. */
|
|
91793
|
+
noteConnected() {
|
|
91794
|
+
this.connectedAt = this.now();
|
|
91795
|
+
this.lastFrameAt = this.now();
|
|
91796
|
+
}
|
|
91797
|
+
/** Record that the transport reports itself disconnected. */
|
|
91798
|
+
noteDisconnected() {
|
|
91799
|
+
this.lastFrameAt = 0;
|
|
91800
|
+
}
|
|
91801
|
+
/** Record an active ping being sent. */
|
|
91802
|
+
notePing() {
|
|
91803
|
+
this.lastPingAt = this.now();
|
|
91804
|
+
}
|
|
91805
|
+
sinceLastFrame() {
|
|
91806
|
+
const base = this.lastFrameAt || this.connectedAt || this.startedAt;
|
|
91807
|
+
return this.now() - base;
|
|
91808
|
+
}
|
|
91809
|
+
/**
|
|
91810
|
+
* Decide what to do about the current socket.
|
|
91811
|
+
*
|
|
91812
|
+
* - `disconnected` — the transport already knows it is down; reconnect.
|
|
91813
|
+
* - `dead` — connected on paper, silent past the window, and a ping raised no
|
|
91814
|
+
* traffic within the grace period; reconnect.
|
|
91815
|
+
* - `suspect` — silent past the window but a ping was only just sent; wait.
|
|
91816
|
+
* - `quiet` — silent but inside the window; normal.
|
|
91817
|
+
* - `healthy` — recent traffic.
|
|
91818
|
+
*/
|
|
91819
|
+
verdict(connected) {
|
|
91820
|
+
if (!connected) return "disconnected";
|
|
91821
|
+
const silent = this.sinceLastFrame();
|
|
91822
|
+
if (silent < SILENT_WINDOW_MS / 2) return "healthy";
|
|
91823
|
+
if (silent < SILENT_WINDOW_MS) return "quiet";
|
|
91824
|
+
if (this.lastPingAt && this.now() - this.lastPingAt < PING_GRACE_MS) return "suspect";
|
|
91825
|
+
return "dead";
|
|
91826
|
+
}
|
|
91827
|
+
/** A snapshot for diagnostics. */
|
|
91828
|
+
state(connected) {
|
|
91829
|
+
return {
|
|
91830
|
+
connected,
|
|
91831
|
+
sinceLastFrameMs: this.sinceLastFrame(),
|
|
91832
|
+
frames: this.frames,
|
|
91833
|
+
probes: this.probes,
|
|
91834
|
+
forcedReconnects: this.forcedReconnects,
|
|
91835
|
+
verdict: this.verdict(connected)
|
|
91836
|
+
};
|
|
91837
|
+
}
|
|
91838
|
+
noteProbe() {
|
|
91839
|
+
this.probes += 1;
|
|
91840
|
+
}
|
|
91841
|
+
noteForcedReconnect() {
|
|
91842
|
+
this.forcedReconnects += 1;
|
|
91843
|
+
}
|
|
91844
|
+
};
|
|
91845
|
+
function startRealtimeSupervisor(options) {
|
|
91846
|
+
const health = new RealtimeHealth();
|
|
91847
|
+
const interval = options.intervalMs ?? 3 * 60 * 1e3;
|
|
91848
|
+
let stopped = false;
|
|
91849
|
+
let timer = null;
|
|
91850
|
+
const evaluate = () => {
|
|
91851
|
+
const connected = options.isConnected();
|
|
91852
|
+
const verdict = health.verdict(connected);
|
|
91853
|
+
health.noteProbe();
|
|
91854
|
+
if (verdict === "dead") {
|
|
91855
|
+
const pingSent = options.ping ? options.ping() : false;
|
|
91856
|
+
if (pingSent) {
|
|
91857
|
+
health.notePing();
|
|
91858
|
+
} else {
|
|
91859
|
+
health.noteForcedReconnect();
|
|
91860
|
+
options.reconnect("realtime socket silent and unpinnable");
|
|
91861
|
+
}
|
|
91862
|
+
}
|
|
91863
|
+
const state = health.state(connected);
|
|
91864
|
+
try {
|
|
91865
|
+
options.onVerdict?.(state);
|
|
91866
|
+
} catch {
|
|
91867
|
+
}
|
|
91868
|
+
return state;
|
|
91869
|
+
};
|
|
91870
|
+
const schedule = () => {
|
|
91871
|
+
if (stopped) return;
|
|
91872
|
+
timer = setTimeout(
|
|
91873
|
+
() => {
|
|
91874
|
+
timer = null;
|
|
91875
|
+
evaluate();
|
|
91876
|
+
schedule();
|
|
91877
|
+
},
|
|
91878
|
+
Math.max(1e3, humanDelay(interval, interval * 0.3))
|
|
91879
|
+
);
|
|
91880
|
+
if (timer && typeof timer.unref === "function") timer.unref();
|
|
91881
|
+
};
|
|
91882
|
+
schedule();
|
|
91883
|
+
return {
|
|
91884
|
+
health,
|
|
91885
|
+
checkNow: evaluate,
|
|
91886
|
+
stop: () => {
|
|
91887
|
+
stopped = true;
|
|
91888
|
+
if (timer) {
|
|
91889
|
+
clearTimeout(timer);
|
|
91890
|
+
timer = null;
|
|
91891
|
+
}
|
|
91892
|
+
}
|
|
91893
|
+
};
|
|
91894
|
+
}
|
|
91895
|
+
|
|
91740
91896
|
// src/utils/index.ts
|
|
91741
91897
|
async function json2(url2, jar2, qs2, options, ctx, customHeader) {
|
|
91742
91898
|
try {
|
|
@@ -91954,6 +92110,7 @@ async function buildAPI(html3, jar2, netData, globalOptions, fbLinkFunc, errorRe
|
|
|
91954
92110
|
const lsd = lsdConfig && lsdConfig.token || extractLsd(html3) || void 0;
|
|
91955
92111
|
const spin = extractSpin(html3);
|
|
91956
92112
|
const revision = spin.__rev || getFrom(html3, 'revision":', ",").trim();
|
|
92113
|
+
const asbdId = extractAsbdId(html3) || void 0;
|
|
91957
92114
|
const clientIDData = findConfig("MqttWebDeviceID");
|
|
91958
92115
|
const clientID = clientIDData ? clientIDData.clientID : void 0;
|
|
91959
92116
|
const mqttConfigData = findConfig("MqttWebConfig");
|
|
@@ -91995,7 +92152,8 @@ async function buildAPI(html3, jar2, netData, globalOptions, fbLinkFunc, errorRe
|
|
|
91995
92152
|
...dtsgResult,
|
|
91996
92153
|
...lsd ? { lsd } : {},
|
|
91997
92154
|
...Object.keys(spin).length ? { __spin: spin } : {},
|
|
91998
|
-
...revision ? { revision } : {}
|
|
92155
|
+
...revision ? { revision } : {},
|
|
92156
|
+
...asbdId ? { asbdId } : {}
|
|
91999
92157
|
};
|
|
92000
92158
|
if (lsd && dtsg) {
|
|
92001
92159
|
ctx.__cometTokens = { lsd, fb_dtsg: dtsg, fetchedAt: Date.now(), failures: 0 };
|
|
@@ -100693,7 +100851,9 @@ var listenMqtt_default = (defaultFuncs, api, ctx) => {
|
|
|
100693
100851
|
attempts: 0,
|
|
100694
100852
|
delay: MQTT_CONFIG.INITIAL_RETRY_DELAY,
|
|
100695
100853
|
throttleDelay: MQTT_CONFIG.THROTTLE_DELAY,
|
|
100696
|
-
generation: 0
|
|
100854
|
+
generation: 0,
|
|
100855
|
+
health: null,
|
|
100856
|
+
healthSupervisor: null
|
|
100697
100857
|
};
|
|
100698
100858
|
const log2 = (...args) => log(...args);
|
|
100699
100859
|
function clearReconnectTimer() {
|
|
@@ -100707,6 +100867,13 @@ var listenMqtt_default = (defaultFuncs, api, ctx) => {
|
|
|
100707
100867
|
clearInterval(lifecycle.presenceInterval);
|
|
100708
100868
|
lifecycle.presenceInterval = null;
|
|
100709
100869
|
}
|
|
100870
|
+
if (lifecycle.healthSupervisor) {
|
|
100871
|
+
try {
|
|
100872
|
+
lifecycle.healthSupervisor.stop();
|
|
100873
|
+
} catch (_3) {
|
|
100874
|
+
}
|
|
100875
|
+
lifecycle.healthSupervisor = null;
|
|
100876
|
+
}
|
|
100710
100877
|
clearReconnectTimer();
|
|
100711
100878
|
}
|
|
100712
100879
|
function destroyClient() {
|
|
@@ -100834,6 +101001,8 @@ var listenMqtt_default = (defaultFuncs, api, ctx) => {
|
|
|
100834
101001
|
});
|
|
100835
101002
|
lifecycle.client = client2;
|
|
100836
101003
|
ctx.mqttClient = client2;
|
|
101004
|
+
const health = new RealtimeHealth();
|
|
101005
|
+
lifecycle.health = health;
|
|
100837
101006
|
let connected = false;
|
|
100838
101007
|
let closing = false;
|
|
100839
101008
|
let throttled = false;
|
|
@@ -100853,12 +101022,14 @@ var listenMqtt_default = (defaultFuncs, api, ctx) => {
|
|
|
100853
101022
|
if (generation !== lifecycle.generation) return;
|
|
100854
101023
|
connected = false;
|
|
100855
101024
|
ctx.mqttConnected = false;
|
|
101025
|
+
health.noteDisconnected();
|
|
100856
101026
|
});
|
|
100857
101027
|
client2.on("close", () => {
|
|
100858
101028
|
if (generation !== lifecycle.generation) return;
|
|
100859
101029
|
if (connected) log2("MQTT connection closed.");
|
|
100860
101030
|
connected = false;
|
|
100861
101031
|
ctx.mqttConnected = false;
|
|
101032
|
+
health.noteDisconnected();
|
|
100862
101033
|
if (closing) return;
|
|
100863
101034
|
closing = true;
|
|
100864
101035
|
if (authFailure) {
|
|
@@ -100876,6 +101047,7 @@ var listenMqtt_default = (defaultFuncs, api, ctx) => {
|
|
|
100876
101047
|
client2.on("connect", () => {
|
|
100877
101048
|
if (generation !== lifecycle.generation) return;
|
|
100878
101049
|
connected = true;
|
|
101050
|
+
health.noteConnected();
|
|
100879
101051
|
client2.subscribe(MQTT_TOPICS, (subErr) => {
|
|
100880
101052
|
if (subErr) {
|
|
100881
101053
|
error("Failed to subscribe to MQTT topics:", subErr);
|
|
@@ -100912,6 +101084,49 @@ var listenMqtt_default = (defaultFuncs, api, ctx) => {
|
|
|
100912
101084
|
ctx.mqttConnected = true;
|
|
100913
101085
|
ctx.lastMqttError = null;
|
|
100914
101086
|
resetBackoff();
|
|
101087
|
+
if (lifecycle.healthSupervisor) {
|
|
101088
|
+
try {
|
|
101089
|
+
lifecycle.healthSupervisor.stop();
|
|
101090
|
+
} catch (_3) {
|
|
101091
|
+
}
|
|
101092
|
+
}
|
|
101093
|
+
lifecycle.healthSupervisor = startRealtimeSupervisor({
|
|
101094
|
+
isConnected: () => generation === lifecycle.generation && client2.connected,
|
|
101095
|
+
reconnect: (reason) => {
|
|
101096
|
+
if (generation !== lifecycle.generation) return;
|
|
101097
|
+
if (lifecycle.stopped || ctx.loggedIn === false) return;
|
|
101098
|
+
log2(`MQTT ${reason}; recycling the socket`);
|
|
101099
|
+
try {
|
|
101100
|
+
client2.end(true);
|
|
101101
|
+
} catch (_3) {
|
|
101102
|
+
}
|
|
101103
|
+
const gen = generation;
|
|
101104
|
+
setTimeout(() => {
|
|
101105
|
+
if (gen !== lifecycle.generation) return;
|
|
101106
|
+
if (lifecycle.stopped || ctx.loggedIn === false) return;
|
|
101107
|
+
if (lifecycle.client && lifecycle.client.connected) return;
|
|
101108
|
+
if (lifecycle.reconnectTimer) return;
|
|
101109
|
+
scheduleReconnect("realtime socket recycled");
|
|
101110
|
+
}, 5e3).unref?.();
|
|
101111
|
+
},
|
|
101112
|
+
ping: () => {
|
|
101113
|
+
try {
|
|
101114
|
+
client2.ping();
|
|
101115
|
+
return true;
|
|
101116
|
+
} catch {
|
|
101117
|
+
return false;
|
|
101118
|
+
}
|
|
101119
|
+
},
|
|
101120
|
+
onVerdict: (state) => {
|
|
101121
|
+
if (state.verdict === "dead") {
|
|
101122
|
+
warn(
|
|
101123
|
+
"MQTT health",
|
|
101124
|
+
`silent for ${Math.round(state.sinceLastFrameMs / 1e3)}s; probing`
|
|
101125
|
+
);
|
|
101126
|
+
}
|
|
101127
|
+
}
|
|
101128
|
+
});
|
|
101129
|
+
ctx.__mqttHealth = () => lifecycle.health ? lifecycle.health.state(generation === lifecycle.generation && !!client2.connected) : null;
|
|
100915
101130
|
api.getBotInitialData().then((botData) => {
|
|
100916
101131
|
if (botData) {
|
|
100917
101132
|
log(`Logged in as: ${botData.name || "Facebook User"} (${botData.uid || ctx.userID})`);
|
|
@@ -100922,6 +101137,7 @@ var listenMqtt_default = (defaultFuncs, api, ctx) => {
|
|
|
100922
101137
|
});
|
|
100923
101138
|
client2.on("message", async (topic, message, _packet) => {
|
|
100924
101139
|
try {
|
|
101140
|
+
health.noteFrame();
|
|
100925
101141
|
ctx.lastMessageTime = Date.now();
|
|
100926
101142
|
ctx.messageCount = (ctx.messageCount || 0) + 1;
|
|
100927
101143
|
const jsonMessage = JSON.parse(message.toString());
|
|
@@ -101007,6 +101223,16 @@ var listenMqtt_default = (defaultFuncs, api, ctx) => {
|
|
|
101007
101223
|
this.stop();
|
|
101008
101224
|
if (typeof cb === "function") cb();
|
|
101009
101225
|
}
|
|
101226
|
+
/** Live connection-health snapshot, for diagnostics/alerting. */
|
|
101227
|
+
getHealth() {
|
|
101228
|
+
if (!lifecycle.health) return null;
|
|
101229
|
+
const connected = !lifecycle.stopped && !!lifecycle.client && !!lifecycle.client.connected;
|
|
101230
|
+
return lifecycle.health.state(connected);
|
|
101231
|
+
}
|
|
101232
|
+
/** Is this listener currently stopped? */
|
|
101233
|
+
isStopped() {
|
|
101234
|
+
return lifecycle.stopped;
|
|
101235
|
+
}
|
|
101010
101236
|
}
|
|
101011
101237
|
const msgEmitter = new MessageEmitter();
|
|
101012
101238
|
lifecycle.stopped = false;
|
|
@@ -101328,6 +101554,22 @@ async function loginHelper(credentials, globalOptions, callback, setOptionsFunc,
|
|
|
101328
101554
|
{
|
|
101329
101555
|
ctx,
|
|
101330
101556
|
isAuthenticated: () => hasAuthenticatedSession(ctx.jar),
|
|
101557
|
+
probeLiveness: async () => {
|
|
101558
|
+
try {
|
|
101559
|
+
const res = await defaultFuncs.get(
|
|
101560
|
+
"https://www.facebook.com/",
|
|
101561
|
+
ctx.jar,
|
|
101562
|
+
null,
|
|
101563
|
+
ctx.globalOptions,
|
|
101564
|
+
{ noRef: true }
|
|
101565
|
+
);
|
|
101566
|
+
const html3 = typeof res === "string" ? res : res && res.body;
|
|
101567
|
+
if (typeof html3 !== "string" || !html3) return true;
|
|
101568
|
+
return !isLoggedOutDocument(html3);
|
|
101569
|
+
} catch {
|
|
101570
|
+
return true;
|
|
101571
|
+
}
|
|
101572
|
+
},
|
|
101331
101573
|
refresh: () => repairSession(ctx, defaultFuncs),
|
|
101332
101574
|
canRelogin: Boolean(credentials.email && credentials.password),
|
|
101333
101575
|
relogin: async () => {
|
|
@@ -101556,4 +101798,4 @@ lodash/lodash.js:
|
|
|
101556
101798
|
*)
|
|
101557
101799
|
*/
|
|
101558
101800
|
|
|
101559
|
-
export { RecoverySupervisor, acquireCookies, applySetCookie, awaitProactiveSlot, canActProactively, collectSessionCookies, index_default as default, defaultBrowserProfile, ensureCometTokens, extractCookies, extractCookiesFromBody, extractCookiesFromJar, extractCookiesFromSetCookie, extractCurrentUser, getFingerprintParams, getHeaders, getWebSocketHeaders, hasAuthenticatedSession, humanDelay, invalidateCometTokens, isAuthFailure, isBreakerOpen, isStaleTokenError, login, missingSessionCookies, normalizeBrowserProfile, noteAction, noteAuthFailure, noteCheckpoint, noteRateLimit, paceRequest, public_exports as publicTypes, randomUserAgent, refreshSessionCookies, repairSession, reserveRequest, resetBreaker, resolveProfile, retryDelay, safetySnapshot, sleep, startLiveness, startRecovery, tripBreaker, warmUpSession, withCometTokens, withSessionRetry };
|
|
101801
|
+
export { PING_GRACE_MS, RealtimeHealth, RecoverySupervisor, SILENT_WINDOW_MS, acquireCookies, applySetCookie, awaitProactiveSlot, canActProactively, collectSessionCookies, index_default as default, defaultBrowserProfile, ensureCometTokens, extractCookies, extractCookiesFromBody, extractCookiesFromJar, extractCookiesFromSetCookie, extractCurrentUser, getFingerprintParams, getHeaders, getWebSocketHeaders, hasAuthenticatedSession, humanDelay, invalidateCometTokens, isAuthFailure, isBreakerOpen, isLoggedOutDocument, isStaleTokenError, login, missingSessionCookies, normalizeBrowserProfile, noteAction, noteAuthFailure, noteCheckpoint, noteRateLimit, paceRequest, public_exports as publicTypes, randomUserAgent, refreshSessionCookies, repairSession, reserveRequest, resetBreaker, resolveProfile, retryDelay, safetySnapshot, sleep, startLiveness, startRealtimeSupervisor, startRecovery, tripBreaker, warmUpSession, withCometTokens, withSessionRetry };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyneoaz/metachat",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.8",
|
|
4
4
|
"description": "A modern TypeScript Facebook Chat API client for building reliable Messenger bots, with built-in anti-detection protection, automatic cookie refresh, music search, and AI theme support.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"prepublishOnly": "npm run build",
|
|
58
58
|
"lint": "npm run typecheck",
|
|
59
59
|
"format": "prettier --write \"src/**/*.ts\" \"index.ts\"",
|
|
60
|
-
"test": "npm run build && node test/smoke.test.cjs && node test/smoke.test.mjs && node test/music-search.test.cjs && node test/mqtt-lifecycle.test.cjs && node test/session-reliability.test.cjs && node test/anti-detection.test.cjs && node test/account-safety.test.cjs && node test/liveness.test.cjs && node test/recovery.test.cjs && node test/cookie-acquisition.test.cjs",
|
|
60
|
+
"test": "npm run build && node test/smoke.test.cjs && node test/smoke.test.mjs && node test/music-search.test.cjs && node test/mqtt-lifecycle.test.cjs && node test/session-reliability.test.cjs && node test/anti-detection.test.cjs && node test/account-safety.test.cjs && node test/liveness.test.cjs && node test/recovery.test.cjs && node test/cookie-acquisition.test.cjs && node test/realtime-health.test.cjs",
|
|
61
61
|
"audit": "npm audit",
|
|
62
62
|
"audit:fix": "npm audit fix"
|
|
63
63
|
},
|