@lazyneoaz/metachat 1.0.10 → 1.1.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.
- package/package.json +1 -1
- package/src/apis/addUserToGroup.js +1 -1
- package/src/apis/changeAdminStatus.js +46 -55
- package/src/apis/changeArchivedStatus.js +16 -8
- package/src/apis/changeAvatar.js +2 -2
- package/src/apis/changeBio.js +1 -1
- package/src/apis/changeBlockedStatus.js +1 -1
- package/src/apis/changeGroupImage.js +23 -15
- package/src/apis/changeThreadColor.js +1 -1
- package/src/apis/changeThreadEmoji.js +14 -8
- package/src/apis/createNewGroup.js +13 -5
- package/src/apis/createPoll.js +14 -5
- package/src/apis/deleteMessage.js +11 -8
- package/src/apis/deleteThread.js +1 -1
- package/src/apis/editMessage.js +59 -48
- package/src/apis/emoji.js +2 -2
- package/src/apis/follow.js +26 -30
- package/src/apis/forwardAttachment.js +3 -2
- package/src/apis/forwardMessage.js +1 -1
- package/src/apis/gcmember.js +4 -3
- package/src/apis/gcname.js +3 -3
- package/src/apis/gcrule.js +4 -3
- package/src/apis/getFriendsList.js +2 -2
- package/src/apis/getThreadInfo.js +1 -1
- package/src/apis/getThreadPictures.js +1 -1
- package/src/apis/handleMessageRequest.js +1 -1
- package/src/apis/listenMqtt.js +160 -6
- package/src/apis/markAsRead.js +15 -9
- package/src/apis/muteThread.js +1 -1
- package/src/apis/nickname.js +62 -48
- package/src/apis/pinMessage.js +110 -130
- package/src/apis/removeUserFromGroup.js +9 -2
- package/src/apis/resolvePhotoUrl.js +1 -1
- package/src/apis/searchForThread.js +1 -1
- package/src/apis/sendEffect.js +3 -2
- package/src/apis/sendMessage.js +13 -2
- package/src/apis/setMessageReaction.js +58 -19
- package/src/apis/setMessageReactionMqtt.js +96 -56
- package/src/apis/setThreadThemeMqtt.js +70 -75
- package/src/apis/shareContact.js +37 -31
- package/src/apis/theme.js +1 -1
- package/src/apis/unfriend.js +1 -1
- package/src/apis/unsendMessage.js +71 -9
- package/src/app/state.js +1 -1
- package/src/engine/models/loginHelper.js +5 -0
- package/src/types/index.d.ts +40 -1
- package/src/utils/antiSuspension.js +5 -5
- package/src/utils/auth-helpers.js +1 -1
- package/src/utils/clients.js +21 -0
- package/src/utils/headers.js +1 -1
- package/src/utils/lsRequest.js +175 -0
- package/src/utils/rateLimiter.js +6 -2
- package/src/utils/tokenRefresh.js +24 -2
package/src/apis/editMessage.js
CHANGED
|
@@ -1,70 +1,81 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* @author RFS-ADRENO
|
|
4
|
-
* @rewrittenBy Isai Ivanov
|
|
5
|
-
*/
|
|
6
2
|
|
|
7
3
|
const utils = require('../utils');
|
|
8
|
-
|
|
9
|
-
function canBeCalled(func) {
|
|
10
|
-
try {
|
|
11
|
-
Reflect.apply(func, null, []);
|
|
12
|
-
return true;
|
|
13
|
-
} catch (error) {
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
4
|
+
const { publishLsRequestWithAck } = require('../utils/lsRequest');
|
|
17
5
|
|
|
18
6
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* @param {string}
|
|
22
|
-
* @param {
|
|
23
|
-
|
|
24
|
-
|
|
7
|
+
* Edits a previously sent bot message via MQTT with ACK confirmation.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} text New text content for the message.
|
|
10
|
+
* @param {string} messageID The ID of the message to edit.
|
|
11
|
+
* @param {Function} [callback] Optional callback(err, result).
|
|
12
|
+
* @returns {Promise}
|
|
13
|
+
*/
|
|
25
14
|
module.exports = function (defaultFuncs, api, ctx) {
|
|
26
15
|
return function editMessage(text, messageID, callback) {
|
|
27
|
-
|
|
28
|
-
|
|
16
|
+
let resolveFunc, rejectFunc;
|
|
17
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
18
|
+
resolveFunc = resolve;
|
|
19
|
+
rejectFunc = reject;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const cb = (typeof callback === "function")
|
|
23
|
+
? (err, data) => { callback(err, data); if (err) rejectFunc(err); else resolveFunc(data); }
|
|
24
|
+
: (err, data) => { if (err) rejectFunc(err); else resolveFunc(data); };
|
|
25
|
+
|
|
26
|
+
if (!ctx.mqttClient || !ctx.mqttClient.connected) {
|
|
27
|
+
return cb(new Error("Not connected to MQTT — call listenMqtt() first")), returnPromise;
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
ctx.wsReqNumber
|
|
32
|
-
ctx.wsTaskNumber
|
|
30
|
+
if (typeof ctx.wsReqNumber !== "number") ctx.wsReqNumber = 0;
|
|
31
|
+
if (typeof ctx.wsTaskNumber !== "number") ctx.wsTaskNumber = 0;
|
|
33
32
|
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
text
|
|
37
|
-
};
|
|
33
|
+
const requestId = ++ctx.wsReqNumber;
|
|
34
|
+
const taskId = ++ctx.wsTaskNumber;
|
|
38
35
|
|
|
39
|
-
const
|
|
36
|
+
const task = {
|
|
40
37
|
failure_count: null,
|
|
41
|
-
label:
|
|
42
|
-
payload: JSON.stringify(
|
|
43
|
-
queue_name:
|
|
44
|
-
task_id:
|
|
38
|
+
label: "742",
|
|
39
|
+
payload: JSON.stringify({ message_id: messageID, text }),
|
|
40
|
+
queue_name: "edit_message",
|
|
41
|
+
task_id: taskId
|
|
45
42
|
};
|
|
46
43
|
|
|
47
|
-
const
|
|
48
|
-
app_id:
|
|
49
|
-
payload: {
|
|
44
|
+
const content = {
|
|
45
|
+
app_id: "2220391788200892",
|
|
46
|
+
payload: JSON.stringify({
|
|
50
47
|
data_trace_id: null,
|
|
51
48
|
epoch_id: parseInt(utils.generateOfflineThreadingID()),
|
|
52
|
-
tasks: [
|
|
53
|
-
version_id:
|
|
54
|
-
},
|
|
55
|
-
request_id:
|
|
49
|
+
tasks: [task],
|
|
50
|
+
version_id: "6903494529735864"
|
|
51
|
+
}),
|
|
52
|
+
request_id: requestId,
|
|
56
53
|
type: 3
|
|
57
54
|
};
|
|
58
55
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
56
|
+
publishLsRequestWithAck({
|
|
57
|
+
client: ctx.mqttClient,
|
|
58
|
+
content,
|
|
59
|
+
requestId,
|
|
60
|
+
timeoutMs: 10000,
|
|
61
|
+
extract: (message) => ({
|
|
62
|
+
success: true,
|
|
63
|
+
messageID,
|
|
64
|
+
text,
|
|
65
|
+
ack: message.payload
|
|
66
|
+
})
|
|
67
|
+
}).then((result) => {
|
|
68
|
+
cb(null, result);
|
|
69
|
+
}).catch((err) => {
|
|
70
|
+
if (err && err.message && err.message.includes("Timeout waiting for LS ACK")) {
|
|
71
|
+
utils.warn("editMessage", "ACK timed out — edit may still have been applied");
|
|
72
|
+
cb(null, { success: true, messageID, text, ackTimeout: true });
|
|
73
|
+
} else {
|
|
74
|
+
utils.error("editMessage", err && err.message ? err.message : err);
|
|
75
|
+
cb(err instanceof Error ? err : new Error(String(err && err.message ? err.message : err)));
|
|
76
|
+
}
|
|
67
77
|
});
|
|
78
|
+
|
|
79
|
+
return returnPromise;
|
|
68
80
|
};
|
|
69
81
|
};
|
|
70
|
-
|
package/src/apis/emoji.js
CHANGED
|
@@ -69,7 +69,7 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
69
69
|
return _callback(new Error("An emoji character is required."));
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
if (!ctx.mqttClient) {
|
|
72
|
+
if (!ctx.mqttClient || !ctx.mqttClient.connected) {
|
|
73
73
|
return _callback(new Error("Not connected to MQTT"));
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -92,7 +92,7 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
92
92
|
};
|
|
93
93
|
|
|
94
94
|
const context = {
|
|
95
|
-
app_id: ctx.appID,
|
|
95
|
+
app_id: ctx.appID || '2220391788200892',
|
|
96
96
|
payload: {
|
|
97
97
|
epoch_id: parseInt(utils.generateOfflineThreadingID()),
|
|
98
98
|
tasks: [query],
|
package/src/apis/follow.js
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Author @YanMaglinte
|
|
5
|
-
* https://github.com/YANDEVA
|
|
6
|
-
* * Example:
|
|
7
|
-
* api.follow("100090794779367", true);
|
|
8
|
-
* jsdocs @ChoruOfficial
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @param {object} defaultFuncs The default functions for making API requests.
|
|
13
|
-
* @param {object} api The full API object.
|
|
14
|
-
* @param {object} ctx The context object.
|
|
15
|
-
* @returns {function(senderID: string, boolean: boolean, callback?: (err: any, data?: any) => void): void} The follow function.
|
|
16
|
-
*/
|
|
17
3
|
module.exports = function (defaultFuncs, api, ctx) {
|
|
18
|
-
/**
|
|
19
|
-
* Follows or unfollows a user on Facebook.
|
|
20
|
-
* @param {string} senderID The ID of the user to follow or unfollow.
|
|
21
|
-
* @param {boolean} boolean Set to `true` to follow the user, `false` to unfollow.
|
|
22
|
-
* @param {(err: any, data?: any) => void} [callback] An optional callback function.
|
|
23
|
-
*/
|
|
24
4
|
return function follow(senderID, boolean, callback) {
|
|
5
|
+
let resolveFunc = () => {};
|
|
6
|
+
let rejectFunc = () => {};
|
|
7
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
8
|
+
resolveFunc = resolve;
|
|
9
|
+
rejectFunc = reject;
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
if (!callback) {
|
|
13
|
+
callback = (err, data) => {
|
|
14
|
+
if (err) return rejectFunc(err);
|
|
15
|
+
resolveFunc(data);
|
|
16
|
+
};
|
|
17
|
+
} else {
|
|
18
|
+
const _userCb = callback;
|
|
19
|
+
callback = (err, data) => {
|
|
20
|
+
if (err) { _userCb(err); return rejectFunc(err); }
|
|
21
|
+
_userCb(null, data);
|
|
22
|
+
resolveFunc(data);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
25
26
|
let form;
|
|
26
27
|
if (boolean) {
|
|
27
28
|
form = {
|
|
@@ -67,15 +68,10 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
api.httpPost("https://www.facebook.com/api/graphql/", form, (err, data) => {
|
|
70
|
-
if (err)
|
|
71
|
-
|
|
72
|
-
callback(err);
|
|
73
|
-
}
|
|
74
|
-
} else {
|
|
75
|
-
if (typeof callback === "function") {
|
|
76
|
-
callback(null, data);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
71
|
+
if (err) return callback(err instanceof Error ? err : new Error(String(err && err.message ? err.message : err)));
|
|
72
|
+
callback(null, data);
|
|
79
73
|
});
|
|
74
|
+
|
|
75
|
+
return returnPromise;
|
|
80
76
|
};
|
|
81
|
-
};
|
|
77
|
+
};
|
|
@@ -48,6 +48,7 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
48
48
|
const timestamp = Date.now();
|
|
49
49
|
const tid = String(threadID);
|
|
50
50
|
if (typeof ctx.wsReqNumber !== 'number') ctx.wsReqNumber = 0;
|
|
51
|
+
if (typeof ctx.wsTaskNumber !== 'number') ctx.wsTaskNumber = 0;
|
|
51
52
|
const reqID = ++ctx.wsReqNumber;
|
|
52
53
|
|
|
53
54
|
const sendPayload = {
|
|
@@ -73,7 +74,7 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
73
74
|
label: "46",
|
|
74
75
|
payload: JSON.stringify(sendPayload),
|
|
75
76
|
queue_name: tid,
|
|
76
|
-
task_id:
|
|
77
|
+
task_id: ++ctx.wsTaskNumber,
|
|
77
78
|
failure_count: null,
|
|
78
79
|
},
|
|
79
80
|
{
|
|
@@ -84,7 +85,7 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
84
85
|
sync_group: 1,
|
|
85
86
|
}),
|
|
86
87
|
queue_name: tid,
|
|
87
|
-
task_id:
|
|
88
|
+
task_id: ++ctx.wsTaskNumber,
|
|
88
89
|
failure_count: null,
|
|
89
90
|
},
|
|
90
91
|
],
|
|
@@ -38,7 +38,7 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
38
38
|
).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
39
39
|
|
|
40
40
|
if (res && res.error) {
|
|
41
|
-
throw res;
|
|
41
|
+
throw new Error(res.error_msg || res.errorSummary || String(res.error));
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
callback(null, { success: true, forwardedTo: threadIDs });
|
package/src/apis/gcmember.js
CHANGED
|
@@ -54,7 +54,7 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
54
54
|
_callback(null, { type: "error_gc", error: "threadID is required." });
|
|
55
55
|
return returnPromise;
|
|
56
56
|
}
|
|
57
|
-
if (!ctx.mqttClient) {
|
|
57
|
+
if (!ctx.mqttClient || !ctx.mqttClient.connected) {
|
|
58
58
|
_callback(null, { type: "error_gc", error: "Not connected to MQTT" });
|
|
59
59
|
return returnPromise;
|
|
60
60
|
}
|
|
@@ -99,7 +99,7 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
const context = {
|
|
102
|
-
app_id: ctx.appID,
|
|
102
|
+
app_id: ctx.appID || '2220391788200892',
|
|
103
103
|
payload: { epoch_id: parseInt(utils.generateOfflineThreadingID()), tasks: [query], version_id: "24631415369801570" },
|
|
104
104
|
request_id: ctx.wsReqNumber,
|
|
105
105
|
type: 3
|
|
@@ -121,7 +121,8 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
121
121
|
return _callback(null, gcmemberInfo);
|
|
122
122
|
});
|
|
123
123
|
} catch (err) {
|
|
124
|
-
|
|
124
|
+
utils.error("gcmember", err);
|
|
125
|
+
_callback(err instanceof Error ? err : new Error(err.message || "An unknown error occurred."));
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
return returnPromise;
|
package/src/apis/gcname.js
CHANGED
|
@@ -69,7 +69,7 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
69
69
|
return _callback(new Error("newName must be a string."));
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
if (!ctx.mqttClient) {
|
|
72
|
+
if (!ctx.mqttClient || !ctx.mqttClient.connected) {
|
|
73
73
|
return _callback(new Error("Not connected to MQTT"));
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -91,11 +91,11 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
91
91
|
};
|
|
92
92
|
|
|
93
93
|
const context = {
|
|
94
|
-
app_id: ctx.appID,
|
|
94
|
+
app_id: ctx.appID || '2220391788200892',
|
|
95
95
|
payload: {
|
|
96
96
|
epoch_id: parseInt(utils.generateOfflineThreadingID()),
|
|
97
97
|
tasks: [query],
|
|
98
|
-
version_id: '
|
|
98
|
+
version_id: '8798795233522156',
|
|
99
99
|
},
|
|
100
100
|
request_id: ctx.wsReqNumber,
|
|
101
101
|
type: 3,
|
package/src/apis/gcrule.js
CHANGED
|
@@ -46,7 +46,7 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
46
46
|
}
|
|
47
47
|
if (!userID) { _callback(null, { type: "error_gc_rule", error: "userID is required." }); return returnPromise; }
|
|
48
48
|
if (!threadID) { _callback(null, { type: "error_gc_rule", error: "threadID is required." }); return returnPromise; }
|
|
49
|
-
if (!ctx.mqttClient) { _callback(null, { type: "error_gc_rule", error: "Not connected to MQTT" }); return returnPromise; }
|
|
49
|
+
if (!ctx.mqttClient || !ctx.mqttClient.connected) { _callback(null, { type: "error_gc_rule", error: "Not connected to MQTT" }); return returnPromise; }
|
|
50
50
|
|
|
51
51
|
const threadInfo = await api.getThreadInfo(threadID);
|
|
52
52
|
if (!threadInfo) {
|
|
@@ -90,7 +90,7 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
90
90
|
task_id: ctx.wsTaskNumber
|
|
91
91
|
};
|
|
92
92
|
const context = {
|
|
93
|
-
app_id: ctx.appID,
|
|
93
|
+
app_id: ctx.appID || '2220391788200892',
|
|
94
94
|
payload: {
|
|
95
95
|
epoch_id: parseInt(utils.generateOfflineThreadingID()),
|
|
96
96
|
tasks: [query],
|
|
@@ -116,7 +116,8 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
116
116
|
});
|
|
117
117
|
|
|
118
118
|
} catch (err) {
|
|
119
|
-
|
|
119
|
+
utils.error("gcrule", err);
|
|
120
|
+
_callback(err instanceof Error ? err : new Error(err.message || "An unknown error occurred."));
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
return returnPromise;
|
|
@@ -61,11 +61,11 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
61
61
|
).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
62
62
|
|
|
63
63
|
if (!res) {
|
|
64
|
-
throw
|
|
64
|
+
throw new Error("getFriendsList returned empty object.");
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
if (res.error) {
|
|
68
|
-
throw res;
|
|
68
|
+
throw new Error(res.error_msg || res.errorSummary || String(res.error));
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
callback(null, formatData(res.payload));
|
|
@@ -225,7 +225,7 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
225
225
|
.then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
226
226
|
|
|
227
227
|
if (resData.error) {
|
|
228
|
-
throw resData;
|
|
228
|
+
throw new Error(resData.error_msg || resData.errorSummary || String(resData.error));
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
const threadInfos = {};
|
|
@@ -44,7 +44,7 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
44
44
|
).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
45
45
|
|
|
46
46
|
if (res && res.error) {
|
|
47
|
-
throw res;
|
|
47
|
+
throw new Error(res.error_msg || res.errorSummary || String(res.error));
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
return callback(null, res?.payload || res);
|
|
@@ -36,7 +36,7 @@ module.exports = (defaultFuncs, api, ctx) => {
|
|
|
36
36
|
).then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
37
37
|
|
|
38
38
|
if (res && res.error) {
|
|
39
|
-
throw res;
|
|
39
|
+
throw new Error(res.error_msg || res.errorSummary || String(res.error));
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
return callback(null, { success: true, accepted: accept });
|
package/src/apis/listenMqtt.js
CHANGED
|
@@ -331,6 +331,7 @@ async function listenMqtt(defaultFuncs, api, ctx, globalCallback, scheduleReconn
|
|
|
331
331
|
}));
|
|
332
332
|
|
|
333
333
|
mqttClient.on('connect', guard("connect", async () => {
|
|
334
|
+
const wasReconnect = !ctx._mqttConnected && (ctx._reconnectAttempts || 0) > 0;
|
|
334
335
|
if (!ctx._mqttConnected) {
|
|
335
336
|
utils.log("MQTT connected successfully");
|
|
336
337
|
ctx._mqttConnected = true;
|
|
@@ -338,6 +339,17 @@ async function listenMqtt(defaultFuncs, api, ctx, globalCallback, scheduleReconn
|
|
|
338
339
|
ctx._cycling = false;
|
|
339
340
|
ctx._reconnectAttempts = 0;
|
|
340
341
|
ctx._mqttQuickCloseCount = 0;
|
|
342
|
+
|
|
343
|
+
// Update reconnect stats and emit lifecycle events
|
|
344
|
+
if (!ctx._reconnectStats) ctx._reconnectStats = { totalAttempts: 0, lastAttemptAt: null, nextAttemptAt: null, lastSuccessAt: null };
|
|
345
|
+
ctx._reconnectStats.nextAttemptAt = null;
|
|
346
|
+
ctx._reconnectStats.lastSuccessAt = Date.now();
|
|
347
|
+
try {
|
|
348
|
+
if (ctx._emitter) {
|
|
349
|
+
const eventName = wasReconnect ? 'reconnected' : 'connected';
|
|
350
|
+
ctx._emitter.emit(eventName, { timestamp: Date.now(), totalReconnects: ctx._reconnectStats.totalAttempts });
|
|
351
|
+
}
|
|
352
|
+
} catch (_) {}
|
|
341
353
|
if (ctx._reconnectTimer) {
|
|
342
354
|
clearTimeout(ctx._reconnectTimer);
|
|
343
355
|
ctx._reconnectTimer = null;
|
|
@@ -440,7 +452,7 @@ async function listenMqtt(defaultFuncs, api, ctx, globalCallback, scheduleReconn
|
|
|
440
452
|
ctx.syncToken = jsonMessage.syncToken;
|
|
441
453
|
}
|
|
442
454
|
if (jsonMessage.lastIssuedSeqId) {
|
|
443
|
-
ctx.lastSeqId = parseInt(jsonMessage.lastIssuedSeqId);
|
|
455
|
+
ctx.lastSeqId = parseInt(jsonMessage.lastIssuedSeqId, 10);
|
|
444
456
|
}
|
|
445
457
|
|
|
446
458
|
if (jsonMessage.deltas) {
|
|
@@ -487,6 +499,9 @@ async function listenMqtt(defaultFuncs, api, ctx, globalCallback, scheduleReconn
|
|
|
487
499
|
}
|
|
488
500
|
// Save connected state BEFORE clearing it — used for quick-close detection.
|
|
489
501
|
const wasConnected = ctx._mqttConnected;
|
|
502
|
+
try {
|
|
503
|
+
if (ctx._emitter) ctx._emitter.emit('disconnected', { timestamp: Date.now(), wasConnected });
|
|
504
|
+
} catch (_) {}
|
|
490
505
|
ctx._mqttConnected = false;
|
|
491
506
|
if (ctx._ending || ctx._cycling) return;
|
|
492
507
|
|
|
@@ -552,6 +567,15 @@ async function listenMqtt(defaultFuncs, api, ctx, globalCallback, scheduleReconn
|
|
|
552
567
|
ctx._mqttWatchdog = null;
|
|
553
568
|
}
|
|
554
569
|
ctx._mqttConnected = false;
|
|
570
|
+
|
|
571
|
+
if (!ctx._ending && !ctx._cycling && ctx.globalOptions.autoReconnect) {
|
|
572
|
+
try { mqttClient.end(true); } catch (_) {}
|
|
573
|
+
ctx._reconnectAttempts = (ctx._reconnectAttempts || 0) + 1;
|
|
574
|
+
const baseDelay = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 2000;
|
|
575
|
+
const d = computeBackoffDelay(ctx, baseDelay, MQTT_MAX_BACKOFF, MQTT_JITTER_MAX);
|
|
576
|
+
utils.warn("MQTT", `Disconnect received — reconnecting in ${d}ms (attempt ${ctx._reconnectAttempts})`);
|
|
577
|
+
scheduleReconnect(d);
|
|
578
|
+
}
|
|
555
579
|
}));
|
|
556
580
|
|
|
557
581
|
mqttClient.on('offline', guard("offline", () => {
|
|
@@ -583,7 +607,8 @@ const MQTT_DEFAULTS = {
|
|
|
583
607
|
autoReconnect: true,
|
|
584
608
|
watchdogIntervalMs: 60000,
|
|
585
609
|
staleMs: 300000,
|
|
586
|
-
reconnectAfterStop: false
|
|
610
|
+
reconnectAfterStop: false,
|
|
611
|
+
maxReconnectAttempts: 100
|
|
587
612
|
};
|
|
588
613
|
|
|
589
614
|
function mqttConf(ctx, overrides) {
|
|
@@ -756,6 +781,22 @@ module.exports = (defaultFuncs, api, ctx, opts) => {
|
|
|
756
781
|
if (ctx._ending) return;
|
|
757
782
|
if (ctx._reconnectTimer) return;
|
|
758
783
|
utils.warn("MQTT", `Will reconnect in ${ms}ms`);
|
|
784
|
+
|
|
785
|
+
if (!ctx._reconnectStats) ctx._reconnectStats = { totalAttempts: 0, lastAttemptAt: null, nextAttemptAt: null, lastSuccessAt: null };
|
|
786
|
+
ctx._reconnectStats.totalAttempts = (ctx._reconnectStats.totalAttempts || 0) + 1;
|
|
787
|
+
ctx._reconnectStats.lastAttemptAt = Date.now();
|
|
788
|
+
ctx._reconnectStats.nextAttemptAt = Date.now() + ms;
|
|
789
|
+
try {
|
|
790
|
+
if (ctx._emitter) {
|
|
791
|
+
ctx._emitter.emit('reconnecting', {
|
|
792
|
+
attempt: ctx._reconnectAttempts || 0,
|
|
793
|
+
delayMs: ms,
|
|
794
|
+
nextAttemptAt: ctx._reconnectStats.nextAttemptAt,
|
|
795
|
+
timestamp: Date.now()
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
} catch (_) {}
|
|
799
|
+
|
|
759
800
|
ctx._reconnectTimer = setTimeout(() => {
|
|
760
801
|
ctx._reconnectTimer = null;
|
|
761
802
|
getSeqIDWrapper();
|
|
@@ -812,11 +853,67 @@ module.exports = (defaultFuncs, api, ctx, opts) => {
|
|
|
812
853
|
const msg = ((err && err.error) || (err && err.message) || String(err || "")) + detail;
|
|
813
854
|
|
|
814
855
|
if (/Not logged in/i.test(msg)) {
|
|
815
|
-
utils.error("MQTT", "Auth error in getSeqID: Not logged in");
|
|
856
|
+
utils.error("MQTT", "Auth error in getSeqID: Not logged in — attempting recovery...");
|
|
857
|
+
|
|
858
|
+
// Step 1: Try token refresh first (fastest, least invasive)
|
|
859
|
+
let tokenRefreshed = false;
|
|
860
|
+
try {
|
|
861
|
+
if (api.tokenRefreshManager && typeof api.tokenRefreshManager.refreshTokens === 'function') {
|
|
862
|
+
utils.log("MQTT", "getSeqID: refreshing tokens before giving up...");
|
|
863
|
+
await api.tokenRefreshManager.refreshTokens(ctx, defaultFuncs, 'https://www.facebook.com');
|
|
864
|
+
tokenRefreshed = true;
|
|
865
|
+
utils.log("MQTT", "getSeqID: token refresh succeeded, scheduling reconnect");
|
|
866
|
+
}
|
|
867
|
+
} catch (refreshErr) {
|
|
868
|
+
utils.warn("MQTT", `getSeqID: token refresh failed: ${refreshErr && refreshErr.message ? refreshErr.message : refreshErr}`);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
if (tokenRefreshed && ctx.globalOptions.autoReconnect) {
|
|
872
|
+
ctx._reconnectAttempts = Math.max(0, (ctx._reconnectAttempts || 0) - 1);
|
|
873
|
+
const baseDelay = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 3000;
|
|
874
|
+
return scheduleReconnect(baseDelay);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// Step 2: Try full auto re-login (email+password) as last resort
|
|
878
|
+
let reloginOk = false;
|
|
879
|
+
try {
|
|
880
|
+
const { globalAutoReLoginManager } = require('../utils/autoReLogin');
|
|
881
|
+
if (globalAutoReLoginManager && globalAutoReLoginManager.isEnabled && globalAutoReLoginManager.isEnabled()) {
|
|
882
|
+
utils.log("MQTT", "getSeqID: attempting auto re-login...");
|
|
883
|
+
reloginOk = await globalAutoReLoginManager.handleSessionExpiry(api, 'https://www.facebook.com', "MQTT getSeqID Not logged in");
|
|
884
|
+
if (reloginOk) {
|
|
885
|
+
utils.log("MQTT", "getSeqID: re-login succeeded, scheduling MQTT reconnect");
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
} catch (reloginErr) {
|
|
889
|
+
utils.warn("MQTT", `getSeqID: auto re-login failed: ${reloginErr && reloginErr.message ? reloginErr.message : reloginErr}`);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
if (reloginOk && ctx.globalOptions.autoReconnect) {
|
|
893
|
+
ctx._reconnectAttempts = 0;
|
|
894
|
+
const baseDelay = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 5000;
|
|
895
|
+
return scheduleReconnect(baseDelay);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// Both recovery paths exhausted — emit auth error to signal the user
|
|
816
899
|
return emitAuthError("not_logged_in", msg);
|
|
817
900
|
}
|
|
818
901
|
if (/blocked the login|checkpoint|security check|session.*expir|invalid.*session|authentication.*fail|auth.*fail|login.*block|account.*lock|verification.*requir/i.test(msg)) {
|
|
819
902
|
utils.error("MQTT", "Auth error in getSeqID: Session/Login blocked");
|
|
903
|
+
|
|
904
|
+
// Still try token refresh for session expiry before giving up
|
|
905
|
+
try {
|
|
906
|
+
if (api.tokenRefreshManager && typeof api.tokenRefreshManager.refreshTokens === 'function') {
|
|
907
|
+
utils.log("MQTT", "getSeqID: refreshing tokens on session expiry...");
|
|
908
|
+
await api.tokenRefreshManager.refreshTokens(ctx, defaultFuncs, 'https://www.facebook.com');
|
|
909
|
+
if (ctx.globalOptions.autoReconnect) {
|
|
910
|
+
ctx._reconnectAttempts = 0;
|
|
911
|
+
const baseDelay = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 5000;
|
|
912
|
+
return scheduleReconnect(baseDelay);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
} catch (_) {}
|
|
916
|
+
|
|
820
917
|
return emitAuthError("login_blocked", msg);
|
|
821
918
|
}
|
|
822
919
|
|
|
@@ -1011,7 +1108,8 @@ module.exports = (defaultFuncs, api, ctx, opts) => {
|
|
|
1011
1108
|
try {
|
|
1012
1109
|
const LIFECYCLE_EVENTS = [
|
|
1013
1110
|
'sessionExpired', 'checkpoint', 'relogin', 'ready',
|
|
1014
|
-
'account_inactive', 'checkpoint_282', 'checkpoint_956', 'error'
|
|
1111
|
+
'account_inactive', 'checkpoint_282', 'checkpoint_956', 'error',
|
|
1112
|
+
'reconnecting', 'reconnected', 'connected', 'disconnected'
|
|
1015
1113
|
];
|
|
1016
1114
|
for (const event of LIFECYCLE_EVENTS) {
|
|
1017
1115
|
// Use listeners() NOT rawListeners() — rawListeners() returns the
|
|
@@ -1080,8 +1178,64 @@ module.exports = (defaultFuncs, api, ctx, opts) => {
|
|
|
1080
1178
|
|
|
1081
1179
|
ctx.firstListen = false;
|
|
1082
1180
|
|
|
1083
|
-
api.stopListening = msgEmitter.stopListening;
|
|
1084
|
-
api.stopListeningAsync = msgEmitter.stopListeningAsync;
|
|
1181
|
+
api.stopListening = msgEmitter.stopListening.bind(msgEmitter);
|
|
1182
|
+
api.stopListeningAsync = msgEmitter.stopListeningAsync.bind(msgEmitter);
|
|
1183
|
+
|
|
1184
|
+
api.reconnect = function(callback) {
|
|
1185
|
+
let resolveFunc, rejectFunc;
|
|
1186
|
+
const returnPromise = new Promise((resolve, reject) => {
|
|
1187
|
+
resolveFunc = resolve;
|
|
1188
|
+
rejectFunc = reject;
|
|
1189
|
+
});
|
|
1190
|
+
const cb = typeof callback === "function"
|
|
1191
|
+
? (err) => { callback(err); if (err) rejectFunc(err); else resolveFunc(); }
|
|
1192
|
+
: (err) => { if (err) rejectFunc(err); else resolveFunc(); };
|
|
1193
|
+
|
|
1194
|
+
if (ctx._ending) {
|
|
1195
|
+
return cb(new Error("listenMqtt is not active — call listenMqtt() first")), returnPromise;
|
|
1196
|
+
}
|
|
1197
|
+
utils.log("MQTT", "api.reconnect() — force-reconnecting");
|
|
1198
|
+
try { if (ctx.mqttClient) ctx.mqttClient.end(true); } catch (_) {}
|
|
1199
|
+
ctx._mqttConnected = false;
|
|
1200
|
+
ctx._reconnectAttempts = 0;
|
|
1201
|
+
if (ctx._reconnectTimer) {
|
|
1202
|
+
clearTimeout(ctx._reconnectTimer);
|
|
1203
|
+
ctx._reconnectTimer = null;
|
|
1204
|
+
}
|
|
1205
|
+
try {
|
|
1206
|
+
if (ctx._emitter) ctx._emitter.emit('reconnecting', { forced: true, attempt: 0, delayMs: 0, timestamp: Date.now() });
|
|
1207
|
+
} catch (_) {}
|
|
1208
|
+
getSeqIDWrapper().then(() => cb(null)).catch(cb);
|
|
1209
|
+
return returnPromise;
|
|
1210
|
+
};
|
|
1211
|
+
|
|
1212
|
+
api.getReconnectStatus = function() {
|
|
1213
|
+
const stats = ctx._reconnectStats || {};
|
|
1214
|
+
return {
|
|
1215
|
+
connected: !!(ctx.mqttClient && ctx.mqttClient.connected),
|
|
1216
|
+
reconnectAttempts: ctx._reconnectAttempts || 0,
|
|
1217
|
+
totalReconnects: stats.totalAttempts || 0,
|
|
1218
|
+
lastAttemptAt: stats.lastAttemptAt || null,
|
|
1219
|
+
nextAttemptAt: ctx._reconnectTimer ? (stats.nextAttemptAt || null) : null,
|
|
1220
|
+
lastSuccessAt: stats.lastSuccessAt || null,
|
|
1221
|
+
reconnectPending: !!ctx._reconnectTimer,
|
|
1222
|
+
autoReconnect: !!ctx.globalOptions.autoReconnect,
|
|
1223
|
+
options: Object.assign({}, ctx._mqttOpt || {})
|
|
1224
|
+
};
|
|
1225
|
+
};
|
|
1226
|
+
|
|
1227
|
+
api.setReconnectOptions = function(newOpts) {
|
|
1228
|
+
if (!newOpts || typeof newOpts !== "object") return;
|
|
1229
|
+
ctx._mqttOpt = Object.assign({}, MQTT_DEFAULTS, ctx._mqttOpt || {}, newOpts);
|
|
1230
|
+
if (typeof ctx._mqttOpt.autoReconnect === "boolean") {
|
|
1231
|
+
ctx.globalOptions.autoReconnect = ctx._mqttOpt.autoReconnect;
|
|
1232
|
+
}
|
|
1233
|
+
if (typeof ctx._mqttOpt.cycleMs === "number") {
|
|
1234
|
+
conf = Object.assign({}, conf, { cycleMs: ctx._mqttOpt.cycleMs });
|
|
1235
|
+
}
|
|
1236
|
+
utils.log("MQTT", "setReconnectOptions applied:", JSON.stringify(ctx._mqttOpt));
|
|
1237
|
+
return ctx._mqttOpt;
|
|
1238
|
+
};
|
|
1085
1239
|
|
|
1086
1240
|
api.useMiddleware = function(nameOrFn, fn) {
|
|
1087
1241
|
const remove = ctx._middleware.use(nameOrFn, fn);
|
package/src/apis/markAsRead.js
CHANGED
|
@@ -21,7 +21,18 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
21
21
|
|
|
22
22
|
if (!callback) {
|
|
23
23
|
callback = (err) => {
|
|
24
|
-
if (err) return rejectFunc(err instanceof Error ? err : new Error(String(err.error || err.message || err)));
|
|
24
|
+
if (err) return rejectFunc(err instanceof Error ? err : new Error(String(err && (err.error || err.message) || err)));
|
|
25
|
+
resolveFunc(null);
|
|
26
|
+
};
|
|
27
|
+
} else {
|
|
28
|
+
const _userCb = callback;
|
|
29
|
+
callback = (err) => {
|
|
30
|
+
if (err) {
|
|
31
|
+
const e = err instanceof Error ? err : new Error(String(err && (err.error || err.message) || err));
|
|
32
|
+
_userCb(e);
|
|
33
|
+
return rejectFunc(e);
|
|
34
|
+
}
|
|
35
|
+
_userCb(null);
|
|
25
36
|
resolveFunc(null);
|
|
26
37
|
};
|
|
27
38
|
}
|
|
@@ -43,9 +54,7 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
43
54
|
.then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
44
55
|
|
|
45
56
|
if (resData && resData.error) {
|
|
46
|
-
|
|
47
|
-
callback(err);
|
|
48
|
-
return returnPromise;
|
|
57
|
+
return callback(new Error(String(resData.error.message || resData.error)));
|
|
49
58
|
}
|
|
50
59
|
|
|
51
60
|
callback(null);
|
|
@@ -62,16 +71,13 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
|
62
71
|
.then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
63
72
|
|
|
64
73
|
if (resData && resData.error) {
|
|
65
|
-
|
|
66
|
-
callback(err);
|
|
67
|
-
return returnPromise;
|
|
74
|
+
return callback(new Error(String(resData.error.message || resData.error)));
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
callback(null);
|
|
71
78
|
}
|
|
72
79
|
} catch (e) {
|
|
73
|
-
|
|
74
|
-
callback(err);
|
|
80
|
+
callback(e instanceof Error ? e : new Error(String(e && (e.error || e.message) || e)));
|
|
75
81
|
}
|
|
76
82
|
|
|
77
83
|
return returnPromise;
|