@dongdev/fca-unofficial 2.0.22 → 2.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/src/api/socket/core/connectMqtt.js +90 -80
- package/src/api/socket/listenMqtt.js +63 -47
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
@@ -1,41 +1,42 @@
|
|
1
1
|
"use strict";
|
2
2
|
const { formatID } = require("../../../utils/format");
|
3
|
+
const uuid = require("uuid");
|
4
|
+
"use strict";
|
3
5
|
module.exports = function createListenMqtt(deps) {
|
4
|
-
const { WebSocket, mqtt, HttpsProxyAgent, buildStream, buildProxy,
|
6
|
+
const { WebSocket, mqtt, HttpsProxyAgent, buildStream, buildProxy,
|
7
|
+
topics, parseDelta, getTaskResponseData, logger
|
8
|
+
} = deps;
|
9
|
+
|
5
10
|
return function listenMqtt(defaultFuncs, api, ctx, globalCallback) {
|
11
|
+
|
12
|
+
function scheduleReconnect(delayMs) {
|
13
|
+
const d = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 2000;
|
14
|
+
const ms = typeof delayMs === "number" ? delayMs : d;
|
15
|
+
if (ctx._reconnectTimer) return; // debounce
|
16
|
+
logger(`mqtt will reconnect in ${ms}ms`, "warn");
|
17
|
+
ctx._reconnectTimer = setTimeout(() => {
|
18
|
+
ctx._reconnectTimer = null;
|
19
|
+
listenMqtt(defaultFuncs, api, ctx, globalCallback);
|
20
|
+
}, ms);
|
21
|
+
}
|
22
|
+
function isEndingLikeError(msg) {
|
23
|
+
return /No subscription existed|client disconnecting|socket hang up|ECONNRESET/i.test(msg || "");
|
24
|
+
}
|
25
|
+
|
6
26
|
const chatOn = ctx.globalOptions.online;
|
7
|
-
const foreground = false;
|
8
27
|
const sessionID = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER) + 1;
|
9
28
|
const username = {
|
10
|
-
u: ctx.userID,
|
11
|
-
|
12
|
-
|
13
|
-
fg: foreground,
|
14
|
-
d: ctx.clientId,
|
15
|
-
ct: "websocket",
|
16
|
-
aid: 219994525426954,
|
17
|
-
aids: null,
|
18
|
-
mqtt_sid: "",
|
19
|
-
cp: 3,
|
20
|
-
ecp: 10,
|
21
|
-
st: [],
|
22
|
-
pm: [],
|
23
|
-
dc: "",
|
24
|
-
no_auto_fg: true,
|
25
|
-
gas: null,
|
26
|
-
pack: [],
|
27
|
-
p: null,
|
28
|
-
php_override: ""
|
29
|
+
u: ctx.userID, s: sessionID, chat_on: chatOn, fg: false, d: ctx.clientId,
|
30
|
+
ct: "websocket", aid: 219994525426954, aids: null, mqtt_sid: "",
|
31
|
+
cp: 3, ecp: 10, st: [], pm: [], dc: "", no_auto_fg: true, gas: null, pack: [], p: null, php_override: ""
|
29
32
|
};
|
33
|
+
|
30
34
|
const cookies = api.getCookies();
|
31
35
|
let host;
|
32
|
-
if (ctx.mqttEndpoint) {
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
} else {
|
37
|
-
host = `wss://edge-chat.facebook.com/chat?sid=${sessionID}&cid=${ctx.clientId}`;
|
38
|
-
}
|
36
|
+
if (ctx.mqttEndpoint) host = `${ctx.mqttEndpoint}&sid=${sessionID}&cid=${ctx.clientId}`;
|
37
|
+
else if (ctx.region) host = `wss://edge-chat.facebook.com/chat?region=${ctx.region.toLowerCase()}&sid=${sessionID}&cid=${ctx.clientId}`;
|
38
|
+
else host = `wss://edge-chat.facebook.com/chat?sid=${sessionID}&cid=${ctx.clientId}`;
|
39
|
+
|
39
40
|
const options = {
|
40
41
|
clientId: "mqttwsclient",
|
41
42
|
protocolId: "MQIsdp",
|
@@ -46,7 +47,7 @@ module.exports = function createListenMqtt(deps) {
|
|
46
47
|
headers: {
|
47
48
|
Cookie: cookies,
|
48
49
|
Origin: "https://www.facebook.com",
|
49
|
-
"User-Agent": ctx.globalOptions.userAgent || "Mozilla/5.0
|
50
|
+
"User-Agent": ctx.globalOptions.userAgent || "Mozilla/5.0",
|
50
51
|
Referer: "https://www.facebook.com/",
|
51
52
|
Host: "edge-chat.facebook.com",
|
52
53
|
Connection: "Upgrade",
|
@@ -64,115 +65,124 @@ module.exports = function createListenMqtt(deps) {
|
|
64
65
|
},
|
65
66
|
keepalive: 30,
|
66
67
|
reschedulePings: true,
|
67
|
-
reconnectPeriod:
|
68
|
+
reconnectPeriod: 0,
|
68
69
|
connectTimeout: 5000
|
69
70
|
};
|
70
71
|
if (ctx.globalOptions.proxy !== undefined) {
|
71
72
|
const agent = new HttpsProxyAgent(ctx.globalOptions.proxy);
|
72
73
|
options.wsOptions.agent = agent;
|
73
74
|
}
|
74
|
-
|
75
|
+
|
76
|
+
ctx.mqttClient = new mqtt.Client(
|
77
|
+
() => buildStream(options, new WebSocket(host, options.wsOptions), buildProxy()),
|
78
|
+
options
|
79
|
+
);
|
75
80
|
const mqttClient = ctx.mqttClient;
|
76
81
|
global.mqttClient = mqttClient;
|
82
|
+
|
83
|
+
|
77
84
|
mqttClient.on("error", function (err) {
|
78
85
|
const msg = String(err && err.message ? err.message : err || "");
|
79
|
-
|
80
|
-
|
86
|
+
|
87
|
+
if ((ctx._ending || ctx._cycling) && isEndingLikeError(msg)) {
|
88
|
+
logger(`mqtt expected during shutdown: ${msg}`, "info");
|
81
89
|
return;
|
82
90
|
}
|
83
|
-
logger(`
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
91
|
+
logger(`mqtt error: ${msg}`, "error");
|
92
|
+
try { mqttClient.end(true); } catch (_) { }
|
93
|
+
if (ctx._ending || ctx._cycling) return;
|
94
|
+
|
95
|
+
if (ctx.globalOptions.autoReconnect) {
|
96
|
+
scheduleReconnect();
|
97
|
+
} else {
|
98
|
+
|
99
|
+
globalCallback({ type: "stop_listen", error: msg || "Connection refused: Server unavailable" }, null);
|
100
|
+
}
|
88
101
|
});
|
102
|
+
|
89
103
|
mqttClient.on("connect", function () {
|
90
104
|
if (process.env.OnStatus === undefined) {
|
91
105
|
logger("fca-unoffcial premium", "info");
|
92
106
|
process.env.OnStatus = true;
|
93
107
|
}
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
}
|
108
|
+
ctx._cycling = false;
|
109
|
+
|
110
|
+
topics.forEach(t => mqttClient.subscribe(t));
|
111
|
+
|
112
|
+
|
113
|
+
const queue = {
|
114
|
+
sync_api_version: 11, max_deltas_able_to_process: 100, delta_batch_size: 500,
|
115
|
+
encoding: "JSON", entity_fbid: ctx.userID, initial_titan_sequence_id: ctx.lastSeqId, device_params: null
|
116
|
+
};
|
117
|
+
const topic = ctx.syncToken ? "/messenger_sync_get_diffs" : "/messenger_sync_create_queue";
|
118
|
+
if (ctx.syncToken) { queue.last_seq_id = ctx.lastSeqId; queue.sync_token = ctx.syncToken; }
|
106
119
|
mqttClient.publish(topic, JSON.stringify(queue), { qos: 1, retain: false });
|
107
120
|
mqttClient.publish("/foreground_state", JSON.stringify({ foreground: chatOn }), { qos: 1 });
|
108
121
|
mqttClient.publish("/set_client_settings", JSON.stringify({ make_user_available_when_in_foreground: true }), { qos: 1 });
|
122
|
+
const d = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 2000;
|
109
123
|
const rTimeout = setTimeout(function () {
|
110
|
-
|
111
|
-
|
124
|
+
logger(`mqtt t_ms timeout, cycling in ${d}ms`, "warn");
|
125
|
+
try { mqttClient.end(true); } catch (_) { }
|
126
|
+
scheduleReconnect(d);
|
112
127
|
}, 5000);
|
128
|
+
|
113
129
|
ctx.tmsWait = function () {
|
114
130
|
clearTimeout(rTimeout);
|
115
|
-
ctx.globalOptions.emitReady
|
131
|
+
if (ctx.globalOptions.emitReady) globalCallback({ type: "ready", error: null });
|
116
132
|
delete ctx.tmsWait;
|
117
133
|
};
|
118
134
|
});
|
119
|
-
|
135
|
+
|
136
|
+
mqttClient.on("message", function (topic, message) {
|
120
137
|
try {
|
121
138
|
let jsonMessage = Buffer.isBuffer(message) ? Buffer.from(message).toString() : message;
|
122
|
-
try {
|
123
|
-
|
124
|
-
} catch (e) {
|
125
|
-
jsonMessage = {};
|
126
|
-
}
|
139
|
+
try { jsonMessage = JSON.parse(jsonMessage); } catch (_) { jsonMessage = {}; }
|
140
|
+
|
127
141
|
if (jsonMessage.type === "jewel_requests_add") {
|
128
142
|
globalCallback(null, { type: "friend_request_received", actorFbId: jsonMessage.from.toString(), timestamp: Date.now().toString() });
|
129
143
|
} else if (jsonMessage.type === "jewel_requests_remove_old") {
|
130
144
|
globalCallback(null, { type: "friend_request_cancel", actorFbId: jsonMessage.from.toString(), timestamp: Date.now().toString() });
|
131
145
|
} else if (topic === "/t_ms") {
|
132
|
-
if (ctx.tmsWait && typeof ctx.tmsWait == "function")
|
133
|
-
ctx.tmsWait();
|
134
|
-
}
|
146
|
+
if (ctx.tmsWait && typeof ctx.tmsWait == "function") ctx.tmsWait();
|
135
147
|
if (jsonMessage.firstDeltaSeqId && jsonMessage.syncToken) {
|
136
148
|
ctx.lastSeqId = jsonMessage.firstDeltaSeqId;
|
137
149
|
ctx.syncToken = jsonMessage.syncToken;
|
138
150
|
}
|
139
|
-
if (jsonMessage.lastIssuedSeqId)
|
140
|
-
|
141
|
-
|
142
|
-
for (const i in jsonMessage.deltas) {
|
143
|
-
const delta = jsonMessage.deltas[i];
|
144
|
-
parseDelta(defaultFuncs, api, ctx, globalCallback, { delta });
|
151
|
+
if (jsonMessage.lastIssuedSeqId) ctx.lastSeqId = parseInt(jsonMessage.lastIssuedSeqId);
|
152
|
+
for (const dlt of (jsonMessage.deltas || [])) {
|
153
|
+
parseDelta(defaultFuncs, api, ctx, globalCallback, { delta: dlt });
|
145
154
|
}
|
146
155
|
} else if (topic === "/thread_typing" || topic === "/orca_typing_notifications") {
|
147
|
-
const typ = {
|
156
|
+
const typ = {
|
157
|
+
type: "typ",
|
158
|
+
isTyping: !!jsonMessage.state,
|
159
|
+
from: jsonMessage.sender_fbid.toString(),
|
160
|
+
threadID: formatID((jsonMessage.thread || jsonMessage.sender_fbid).toString())
|
161
|
+
};
|
148
162
|
globalCallback(null, typ);
|
149
163
|
} else if (topic === "/orca_presence") {
|
150
164
|
if (!ctx.globalOptions.updatePresence) {
|
151
|
-
for (const
|
152
|
-
const
|
153
|
-
const userID = data["u"];
|
154
|
-
const presence = { type: "presence", userID: userID.toString(), timestamp: data["l"] * 1000, statuses: data["p"] };
|
165
|
+
for (const data of (jsonMessage.list || [])) {
|
166
|
+
const presence = { type: "presence", userID: String(data.u), timestamp: data.l * 1000, statuses: data.p };
|
155
167
|
globalCallback(null, presence);
|
156
168
|
}
|
157
169
|
}
|
158
|
-
} else if (topic
|
170
|
+
} else if (topic === "/ls_resp") {
|
159
171
|
const parsedPayload = JSON.parse(jsonMessage.payload);
|
160
172
|
const reqID = jsonMessage.request_id;
|
161
173
|
if (ctx["tasks"].has(reqID)) {
|
162
174
|
const taskData = ctx["tasks"].get(reqID);
|
163
175
|
const { type: taskType, callback: taskCallback } = taskData;
|
164
176
|
const taskRespData = getTaskResponseData(taskType, parsedPayload);
|
165
|
-
if (taskRespData == null)
|
166
|
-
|
167
|
-
} else {
|
168
|
-
taskCallback(null, Object.assign({ type: taskType, reqID: reqID }, taskRespData));
|
169
|
-
}
|
177
|
+
if (taskRespData == null) taskCallback("error", null);
|
178
|
+
else taskCallback(null, Object.assign({ type: taskType, reqID }, taskRespData));
|
170
179
|
}
|
171
180
|
}
|
172
181
|
} catch (ex) {
|
173
|
-
|
182
|
+
logger(`mqtt message parse error: ${ex && ex.message ? ex.message : ex}`, "error");
|
174
183
|
}
|
175
184
|
});
|
185
|
+
|
176
186
|
mqttClient.on("close", function () { });
|
177
187
|
mqttClient.on("disconnect", () => { });
|
178
188
|
};
|
@@ -15,44 +15,54 @@ const getTaskResponseData = require("./core/getTaskResponseData");
|
|
15
15
|
const parseDelta = createParseDelta({ markDelivery, parseAndCheckLogin });
|
16
16
|
const listenMqtt = createListenMqtt({ WebSocket, mqtt, HttpsProxyAgent, buildStream, buildProxy, topics, parseDelta, getTaskResponseData, logger });
|
17
17
|
const getSeqIDFactory = createGetSeqID({ parseAndCheckLogin, listenMqtt, logger });
|
18
|
-
|
18
|
+
|
19
|
+
const MQTT_DEFAULTS = { cycleMs: 60 * 60 * 1000, reconnectDelayMs: 2000, autoReconnect: true, reconnectAfterStop: false };
|
20
|
+
function mqttConf(ctx, overrides) {
|
21
|
+
ctx._mqttOpt = Object.assign({}, MQTT_DEFAULTS, ctx._mqttOpt || {}, overrides || {});
|
22
|
+
if (typeof ctx._mqttOpt.autoReconnect === "boolean") ctx.globalOptions.autoReconnect = ctx._mqttOpt.autoReconnect;
|
23
|
+
return ctx._mqttOpt;
|
24
|
+
}
|
25
|
+
|
26
|
+
module.exports = function (defaultFuncs, api, ctx, opts) {
|
19
27
|
const identity = function () { };
|
20
28
|
let globalCallback = identity;
|
29
|
+
let conf = mqttConf(ctx, opts);
|
30
|
+
|
21
31
|
function getSeqIDWrapper() {
|
22
32
|
const form = {
|
23
|
-
av: ctx.
|
33
|
+
av: ctx.globalOptions.pageID,
|
24
34
|
queries: JSON.stringify({
|
25
|
-
o0: {
|
35
|
+
o0: {
|
36
|
+
doc_id: "3336396659757871",
|
37
|
+
query_params: { limit: 1, before: null, tags: ["INBOX"], includeDeliveryReceipts: false, includeSeqID: true }
|
38
|
+
}
|
26
39
|
})
|
27
40
|
};
|
28
|
-
logger("
|
29
|
-
return getSeqIDFactory(defaultFuncs, api, ctx, globalCallback, form)
|
30
|
-
logger("
|
31
|
-
|
32
|
-
logger(`MQTT getSeqID error: ${e && e.message ? e.message : e}`, "error");
|
33
|
-
});
|
41
|
+
logger("mqtt getSeqID call", "info");
|
42
|
+
return getSeqIDFactory(defaultFuncs, api, ctx, globalCallback, form)
|
43
|
+
.then(() => { logger("mqtt getSeqID done", "info"); ctx._cycling = false; })
|
44
|
+
.catch(e => { logger(`mqtt getSeqID error: ${e && e.message ? e.message : e}`, "error"); });
|
34
45
|
}
|
46
|
+
|
35
47
|
function isConnected() {
|
36
48
|
return !!(ctx.mqttClient && ctx.mqttClient.connected);
|
37
49
|
}
|
50
|
+
|
38
51
|
function unsubAll(cb) {
|
39
52
|
if (!isConnected()) return cb && cb();
|
40
53
|
let pending = topics.length;
|
41
54
|
if (!pending) return cb && cb();
|
42
|
-
let
|
55
|
+
let fired = false;
|
43
56
|
topics.forEach(t => {
|
44
|
-
ctx.mqttClient.unsubscribe(t,
|
45
|
-
|
46
|
-
if (msg && /No subscription existed/i.test(msg)) err = null;
|
47
|
-
if (--pending === 0 && !done) {
|
48
|
-
done = true;
|
49
|
-
cb && cb();
|
50
|
-
}
|
57
|
+
ctx.mqttClient.unsubscribe(t, () => {
|
58
|
+
if (--pending === 0 && !fired) { fired = true; cb && cb(); }
|
51
59
|
});
|
52
60
|
});
|
53
61
|
}
|
62
|
+
|
54
63
|
function endQuietly(next) {
|
55
64
|
const finish = () => {
|
65
|
+
try { ctx.mqttClient && ctx.mqttClient.removeAllListeners(); } catch (_) { }
|
56
66
|
ctx.mqttClient = undefined;
|
57
67
|
ctx.lastSeqId = null;
|
58
68
|
ctx.syncToken = undefined;
|
@@ -62,71 +72,77 @@ module.exports = function (defaultFuncs, api, ctx) {
|
|
62
72
|
};
|
63
73
|
try {
|
64
74
|
if (ctx.mqttClient) {
|
65
|
-
if (isConnected()) {
|
66
|
-
try { ctx.mqttClient.publish("/browser_close", "{}"); } catch (_) { }
|
67
|
-
}
|
75
|
+
if (isConnected()) { try { ctx.mqttClient.publish("/browser_close", "{}"); } catch (_) { } }
|
68
76
|
ctx.mqttClient.end(true, finish);
|
69
77
|
} else finish();
|
70
|
-
} catch (_) {
|
71
|
-
finish();
|
72
|
-
}
|
78
|
+
} catch (_) { finish(); }
|
73
79
|
}
|
80
|
+
|
74
81
|
function delayedReconnect() {
|
75
|
-
|
76
|
-
|
82
|
+
const d = conf.reconnectDelayMs;
|
83
|
+
logger(`mqtt reconnect in ${d}ms`, "info");
|
84
|
+
setTimeout(() => getSeqIDWrapper(), d);
|
77
85
|
}
|
86
|
+
|
78
87
|
function forceCycle() {
|
88
|
+
if (ctx._cycling) return; // đừng cycle chồng
|
89
|
+
ctx._cycling = true;
|
79
90
|
ctx._ending = true;
|
80
|
-
logger("
|
81
|
-
unsubAll(() =>
|
82
|
-
endQuietly(() => {
|
83
|
-
delayedReconnect();
|
84
|
-
});
|
85
|
-
});
|
91
|
+
logger("mqtt force cycle begin", "warn");
|
92
|
+
unsubAll(() => endQuietly(() => delayedReconnect()));
|
86
93
|
}
|
94
|
+
|
87
95
|
return function (callback) {
|
88
96
|
class MessageEmitter extends EventEmitter {
|
89
97
|
stopListening(callback2) {
|
90
98
|
const cb = callback2 || function () { };
|
91
|
-
logger("
|
99
|
+
logger("mqtt stop requested", "info");
|
92
100
|
globalCallback = identity;
|
101
|
+
|
93
102
|
if (ctx._autoCycleTimer) {
|
94
103
|
clearInterval(ctx._autoCycleTimer);
|
95
104
|
ctx._autoCycleTimer = null;
|
96
|
-
logger("
|
105
|
+
logger("mqtt auto-cycle cleared", "info");
|
97
106
|
}
|
107
|
+
|
98
108
|
ctx._ending = true;
|
99
|
-
unsubAll(() => {
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
});
|
109
|
+
unsubAll(() => endQuietly(() => {
|
110
|
+
logger("mqtt stopped", "info");
|
111
|
+
cb();
|
112
|
+
conf = mqttConf(ctx, conf);
|
113
|
+
if (conf.reconnectAfterStop) delayedReconnect();
|
114
|
+
}));
|
106
115
|
}
|
107
116
|
async stopListeningAsync() {
|
108
117
|
return new Promise(resolve => { this.stopListening(resolve); });
|
109
118
|
}
|
110
119
|
}
|
120
|
+
|
111
121
|
const msgEmitter = new MessageEmitter();
|
112
122
|
globalCallback = callback || function (error, message) {
|
113
|
-
if (error) { logger("
|
123
|
+
if (error) { logger("mqtt emit error", "error"); return msgEmitter.emit("error", error); }
|
114
124
|
msgEmitter.emit("message", message);
|
115
125
|
};
|
126
|
+
|
127
|
+
conf = mqttConf(ctx, conf);
|
116
128
|
if (!ctx.firstListen) ctx.lastSeqId = null;
|
117
129
|
ctx.syncToken = undefined;
|
118
130
|
ctx.t_mqttCalled = false;
|
119
|
-
|
120
|
-
|
121
|
-
|
131
|
+
|
132
|
+
if (ctx._autoCycleTimer) { clearInterval(ctx._autoCycleTimer); ctx._autoCycleTimer = null; }
|
133
|
+
if (conf.cycleMs && conf.cycleMs > 0) {
|
134
|
+
ctx._autoCycleTimer = setInterval(forceCycle, conf.cycleMs);
|
135
|
+
logger(`mqtt auto-cycle enabled ${conf.cycleMs}ms`, "info");
|
136
|
+
} else {
|
137
|
+
logger("mqtt auto-cycle disabled", "info");
|
122
138
|
}
|
123
|
-
|
124
|
-
logger("MQTT auto-cycle enabled 3600000ms", "info");
|
139
|
+
|
125
140
|
if (!ctx.firstListen || !ctx.lastSeqId) getSeqIDWrapper();
|
126
141
|
else {
|
127
|
-
logger("
|
142
|
+
logger("mqtt starting listenMqtt", "info");
|
128
143
|
listenMqtt(defaultFuncs, api, ctx, globalCallback);
|
129
144
|
}
|
145
|
+
|
130
146
|
api.stopListening = msgEmitter.stopListening;
|
131
147
|
api.stopListeningAsync = msgEmitter.stopListeningAsync;
|
132
148
|
return msgEmitter;
|