@dongdev/fca-unofficial 2.0.21 → 2.0.23
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/http/httpGet.js +22 -40
- package/src/api/http/httpPost.js +26 -38
- package/src/api/socket/core/connectMqtt.js +91 -80
- package/src/api/socket/listenMqtt.js +63 -47
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
package/src/api/http/httpGet.js
CHANGED
@@ -1,64 +1,46 @@
|
|
1
1
|
"use strict";
|
2
2
|
|
3
|
-
const
|
4
|
-
const { get } = require("../../utils/request");
|
5
|
-
const { getType } = require("../../utils/format");
|
6
|
-
module.exports = function(defaultFuncs, api, ctx) {
|
7
|
-
return function httpGet(url, form, customHeader, callback, notAPI) {
|
8
|
-
let resolveFunc = function() {};
|
9
|
-
let rejectFunc = function() {};
|
3
|
+
const { getType } = require("../../utils/format.js");
|
4
|
+
const { get } = require("../../utils/request.js");
|
10
5
|
|
11
|
-
|
6
|
+
const httpGetFactory = function (defaultFuncs, api, ctx) {
|
7
|
+
return function httpGet(url, form, callback, notAPI) {
|
8
|
+
let resolveFunc = () => { };
|
9
|
+
let rejectFunc = () => { };
|
10
|
+
|
11
|
+
const returnPromise = new Promise((resolve, reject) => {
|
12
12
|
resolveFunc = resolve;
|
13
13
|
rejectFunc = reject;
|
14
14
|
});
|
15
15
|
|
16
16
|
if (
|
17
|
-
|
18
|
-
getType(form)
|
17
|
+
!callback &&
|
18
|
+
(getType(form) === "Function" || getType(form) === "AsyncFunction")
|
19
19
|
) {
|
20
20
|
callback = form;
|
21
21
|
form = {};
|
22
22
|
}
|
23
23
|
|
24
|
-
|
25
|
-
getType(customHeader) == "Function" ||
|
26
|
-
getType(customHeader) == "AsyncFunction"
|
27
|
-
) {
|
28
|
-
callback = customHeader;
|
29
|
-
customHeader = {};
|
30
|
-
}
|
31
|
-
|
32
|
-
customHeader = customHeader || {};
|
24
|
+
form = form || {};
|
33
25
|
|
34
26
|
callback =
|
35
27
|
callback ||
|
36
|
-
function(err, data) {
|
28
|
+
function (err, data) {
|
37
29
|
if (err) return rejectFunc(err);
|
38
30
|
resolveFunc(data);
|
39
31
|
};
|
40
32
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
.
|
47
|
-
|
48
|
-
|
49
|
-
});
|
50
|
-
} else {
|
51
|
-
defaultFuncs
|
52
|
-
.get(url, ctx.jar, form, null, customHeader)
|
53
|
-
.then(function(resData) {
|
54
|
-
callback(null, resData.data.toString());
|
55
|
-
})
|
56
|
-
.catch(function(err) {
|
57
|
-
log.error("httpGet", err);
|
58
|
-
return callback(err);
|
59
|
-
});
|
60
|
-
}
|
33
|
+
const executor = notAPI ? get : defaultFuncs.get;
|
34
|
+
|
35
|
+
executor(url, ctx.jar, form)
|
36
|
+
.then((resData) => callback(null, resData.data))
|
37
|
+
.catch(function (err) {
|
38
|
+
console.error("httpGet", err);
|
39
|
+
return callback(err);
|
40
|
+
});
|
61
41
|
|
62
42
|
return returnPromise;
|
63
43
|
};
|
64
44
|
};
|
45
|
+
|
46
|
+
module.exports = httpGetFactory;
|
package/src/api/http/httpPost.js
CHANGED
@@ -1,64 +1,52 @@
|
|
1
1
|
"use strict";
|
2
2
|
|
3
|
-
const log = require("npmlog");
|
4
3
|
const { post } = require("../../utils/request");
|
5
4
|
const { getType } = require("../../utils/format");
|
6
|
-
module.exports = function(defaultFuncs, api, ctx) {
|
7
|
-
return function httpPost(url, form, customHeader, callback, notAPI) {
|
8
|
-
let resolveFunc = function() {};
|
9
|
-
let rejectFunc = function() {};
|
10
5
|
|
11
|
-
|
6
|
+
const httpPostFactory = function (defaultFuncs, api, ctx) {
|
7
|
+
return function httpPost(url, form, callback, notAPI) {
|
8
|
+
let resolveFunc = () => { };
|
9
|
+
let rejectFunc = () => { };
|
10
|
+
|
11
|
+
const returnPromise = new Promise((resolve, reject) => {
|
12
12
|
resolveFunc = resolve;
|
13
13
|
rejectFunc = reject;
|
14
14
|
});
|
15
15
|
|
16
16
|
if (
|
17
|
-
|
18
|
-
getType(form)
|
17
|
+
!callback &&
|
18
|
+
(getType(form) === "Function" || getType(form) === "AsyncFunction")
|
19
19
|
) {
|
20
20
|
callback = form;
|
21
21
|
form = {};
|
22
22
|
}
|
23
23
|
|
24
|
-
|
25
|
-
getType(customHeader) == "Function" ||
|
26
|
-
getType(customHeader) == "AsyncFunction"
|
27
|
-
) {
|
28
|
-
callback = customHeader;
|
29
|
-
customHeader = {};
|
30
|
-
}
|
31
|
-
|
32
|
-
customHeader = customHeader || {};
|
24
|
+
form = form || {};
|
33
25
|
|
34
26
|
callback =
|
35
27
|
callback ||
|
36
|
-
function(err, data) {
|
28
|
+
function (err, data) {
|
37
29
|
if (err) return rejectFunc(err);
|
38
30
|
resolveFunc(data);
|
39
31
|
};
|
40
32
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
.
|
53
|
-
|
54
|
-
|
55
|
-
})
|
56
|
-
.catch(function(err) {
|
57
|
-
log.error("httpPost", err);
|
58
|
-
return callback(err);
|
59
|
-
});
|
60
|
-
}
|
33
|
+
const executor = notAPI ? post : defaultFuncs.post;
|
34
|
+
|
35
|
+
executor(url, ctx.jar, form, ctx.globalOptions)
|
36
|
+
.then((resData) => {
|
37
|
+
let data = resData.data;
|
38
|
+
if (typeof data === "object") {
|
39
|
+
data = JSON.stringify(data, null, 2);
|
40
|
+
}
|
41
|
+
callback(null, data);
|
42
|
+
})
|
43
|
+
.catch((err) => {
|
44
|
+
console.error("httpPost", err);
|
45
|
+
return callback(err);
|
46
|
+
});
|
61
47
|
|
62
48
|
return returnPromise;
|
63
49
|
};
|
64
50
|
};
|
51
|
+
|
52
|
+
module.exports = httpPostFactory;
|
@@ -1,41 +1,43 @@
|
|
1
1
|
"use strict";
|
2
2
|
const { formatID } = require("../../../utils/format");
|
3
|
+
|
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;
|
28
|
+
const GUID = utils.getGUID();
|
9
29
|
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: ""
|
30
|
+
u: ctx.userID, s: sessionID, chat_on: chatOn, fg: false, d: GUID,
|
31
|
+
ct: "websocket", aid: 219994525426954, aids: null, mqtt_sid: "",
|
32
|
+
cp: 3, ecp: 10, st: [], pm: [], dc: "", no_auto_fg: true, gas: null, pack: [], p: null, php_override: ""
|
29
33
|
};
|
34
|
+
|
30
35
|
const cookies = api.getCookies();
|
31
36
|
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
|
-
}
|
37
|
+
if (ctx.mqttEndpoint) host = `${ctx.mqttEndpoint}&sid=${sessionID}&cid=${GUID}`;
|
38
|
+
else if (ctx.region) host = `wss://edge-chat.facebook.com/chat?region=${ctx.region.toLowerCase()}&sid=${sessionID}&cid=${ctx.clientId}`;
|
39
|
+
else host = `wss://edge-chat.facebook.com/chat?sid=${sessionID}&cid=${GUID}`;
|
40
|
+
|
39
41
|
const options = {
|
40
42
|
clientId: "mqttwsclient",
|
41
43
|
protocolId: "MQIsdp",
|
@@ -46,7 +48,7 @@ module.exports = function createListenMqtt(deps) {
|
|
46
48
|
headers: {
|
47
49
|
Cookie: cookies,
|
48
50
|
Origin: "https://www.facebook.com",
|
49
|
-
"User-Agent": ctx.globalOptions.userAgent || "Mozilla/5.0
|
51
|
+
"User-Agent": ctx.globalOptions.userAgent || "Mozilla/5.0",
|
50
52
|
Referer: "https://www.facebook.com/",
|
51
53
|
Host: "edge-chat.facebook.com",
|
52
54
|
Connection: "Upgrade",
|
@@ -64,115 +66,124 @@ module.exports = function createListenMqtt(deps) {
|
|
64
66
|
},
|
65
67
|
keepalive: 30,
|
66
68
|
reschedulePings: true,
|
67
|
-
reconnectPeriod:
|
69
|
+
reconnectPeriod: 0,
|
68
70
|
connectTimeout: 5000
|
69
71
|
};
|
70
72
|
if (ctx.globalOptions.proxy !== undefined) {
|
71
73
|
const agent = new HttpsProxyAgent(ctx.globalOptions.proxy);
|
72
74
|
options.wsOptions.agent = agent;
|
73
75
|
}
|
74
|
-
|
76
|
+
|
77
|
+
ctx.mqttClient = new mqtt.Client(
|
78
|
+
() => buildStream(options, new WebSocket(host, options.wsOptions), buildProxy()),
|
79
|
+
options
|
80
|
+
);
|
75
81
|
const mqttClient = ctx.mqttClient;
|
76
82
|
global.mqttClient = mqttClient;
|
83
|
+
|
84
|
+
|
77
85
|
mqttClient.on("error", function (err) {
|
78
86
|
const msg = String(err && err.message ? err.message : err || "");
|
79
|
-
|
80
|
-
|
87
|
+
|
88
|
+
if ((ctx._ending || ctx._cycling) && isEndingLikeError(msg)) {
|
89
|
+
logger(`mqtt expected during shutdown: ${msg}`, "info");
|
81
90
|
return;
|
82
91
|
}
|
83
|
-
logger(`
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
92
|
+
logger(`mqtt error: ${msg}`, "error");
|
93
|
+
try { mqttClient.end(true); } catch (_) { }
|
94
|
+
if (ctx._ending || ctx._cycling) return;
|
95
|
+
|
96
|
+
if (ctx.globalOptions.autoReconnect) {
|
97
|
+
scheduleReconnect();
|
98
|
+
} else {
|
99
|
+
|
100
|
+
globalCallback({ type: "stop_listen", error: msg || "Connection refused: Server unavailable" }, null);
|
101
|
+
}
|
88
102
|
});
|
103
|
+
|
89
104
|
mqttClient.on("connect", function () {
|
90
105
|
if (process.env.OnStatus === undefined) {
|
91
106
|
logger("fca-unoffcial premium", "info");
|
92
107
|
process.env.OnStatus = true;
|
93
108
|
}
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
}
|
109
|
+
ctx._cycling = false;
|
110
|
+
|
111
|
+
topics.forEach(t => mqttClient.subscribe(t));
|
112
|
+
|
113
|
+
|
114
|
+
const queue = {
|
115
|
+
sync_api_version: 11, max_deltas_able_to_process: 100, delta_batch_size: 500,
|
116
|
+
encoding: "JSON", entity_fbid: ctx.userID, initial_titan_sequence_id: ctx.lastSeqId, device_params: null
|
117
|
+
};
|
118
|
+
const topic = ctx.syncToken ? "/messenger_sync_get_diffs" : "/messenger_sync_create_queue";
|
119
|
+
if (ctx.syncToken) { queue.last_seq_id = ctx.lastSeqId; queue.sync_token = ctx.syncToken; }
|
106
120
|
mqttClient.publish(topic, JSON.stringify(queue), { qos: 1, retain: false });
|
107
121
|
mqttClient.publish("/foreground_state", JSON.stringify({ foreground: chatOn }), { qos: 1 });
|
108
122
|
mqttClient.publish("/set_client_settings", JSON.stringify({ make_user_available_when_in_foreground: true }), { qos: 1 });
|
123
|
+
const d = (ctx._mqttOpt && ctx._mqttOpt.reconnectDelayMs) || 2000;
|
109
124
|
const rTimeout = setTimeout(function () {
|
110
|
-
|
111
|
-
|
125
|
+
logger(`mqtt t_ms timeout, cycling in ${d}ms`, "warn");
|
126
|
+
try { mqttClient.end(true); } catch (_) { }
|
127
|
+
scheduleReconnect(d);
|
112
128
|
}, 5000);
|
129
|
+
|
113
130
|
ctx.tmsWait = function () {
|
114
131
|
clearTimeout(rTimeout);
|
115
|
-
ctx.globalOptions.emitReady
|
132
|
+
if (ctx.globalOptions.emitReady) globalCallback({ type: "ready", error: null });
|
116
133
|
delete ctx.tmsWait;
|
117
134
|
};
|
118
135
|
});
|
119
|
-
|
136
|
+
|
137
|
+
mqttClient.on("message", function (topic, message) {
|
120
138
|
try {
|
121
139
|
let jsonMessage = Buffer.isBuffer(message) ? Buffer.from(message).toString() : message;
|
122
|
-
try {
|
123
|
-
|
124
|
-
} catch (e) {
|
125
|
-
jsonMessage = {};
|
126
|
-
}
|
140
|
+
try { jsonMessage = JSON.parse(jsonMessage); } catch (_) { jsonMessage = {}; }
|
141
|
+
|
127
142
|
if (jsonMessage.type === "jewel_requests_add") {
|
128
143
|
globalCallback(null, { type: "friend_request_received", actorFbId: jsonMessage.from.toString(), timestamp: Date.now().toString() });
|
129
144
|
} else if (jsonMessage.type === "jewel_requests_remove_old") {
|
130
145
|
globalCallback(null, { type: "friend_request_cancel", actorFbId: jsonMessage.from.toString(), timestamp: Date.now().toString() });
|
131
146
|
} else if (topic === "/t_ms") {
|
132
|
-
if (ctx.tmsWait && typeof ctx.tmsWait == "function")
|
133
|
-
ctx.tmsWait();
|
134
|
-
}
|
147
|
+
if (ctx.tmsWait && typeof ctx.tmsWait == "function") ctx.tmsWait();
|
135
148
|
if (jsonMessage.firstDeltaSeqId && jsonMessage.syncToken) {
|
136
149
|
ctx.lastSeqId = jsonMessage.firstDeltaSeqId;
|
137
150
|
ctx.syncToken = jsonMessage.syncToken;
|
138
151
|
}
|
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 });
|
152
|
+
if (jsonMessage.lastIssuedSeqId) ctx.lastSeqId = parseInt(jsonMessage.lastIssuedSeqId);
|
153
|
+
for (const dlt of (jsonMessage.deltas || [])) {
|
154
|
+
parseDelta(defaultFuncs, api, ctx, globalCallback, { delta: dlt });
|
145
155
|
}
|
146
156
|
} else if (topic === "/thread_typing" || topic === "/orca_typing_notifications") {
|
147
|
-
const typ = {
|
157
|
+
const typ = {
|
158
|
+
type: "typ",
|
159
|
+
isTyping: !!jsonMessage.state,
|
160
|
+
from: jsonMessage.sender_fbid.toString(),
|
161
|
+
threadID: formatID((jsonMessage.thread || jsonMessage.sender_fbid).toString())
|
162
|
+
};
|
148
163
|
globalCallback(null, typ);
|
149
164
|
} else if (topic === "/orca_presence") {
|
150
165
|
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"] };
|
166
|
+
for (const data of (jsonMessage.list || [])) {
|
167
|
+
const presence = { type: "presence", userID: String(data.u), timestamp: data.l * 1000, statuses: data.p };
|
155
168
|
globalCallback(null, presence);
|
156
169
|
}
|
157
170
|
}
|
158
|
-
} else if (topic
|
171
|
+
} else if (topic === "/ls_resp") {
|
159
172
|
const parsedPayload = JSON.parse(jsonMessage.payload);
|
160
173
|
const reqID = jsonMessage.request_id;
|
161
174
|
if (ctx["tasks"].has(reqID)) {
|
162
175
|
const taskData = ctx["tasks"].get(reqID);
|
163
176
|
const { type: taskType, callback: taskCallback } = taskData;
|
164
177
|
const taskRespData = getTaskResponseData(taskType, parsedPayload);
|
165
|
-
if (taskRespData == null)
|
166
|
-
|
167
|
-
} else {
|
168
|
-
taskCallback(null, Object.assign({ type: taskType, reqID: reqID }, taskRespData));
|
169
|
-
}
|
178
|
+
if (taskRespData == null) taskCallback("error", null);
|
179
|
+
else taskCallback(null, Object.assign({ type: taskType, reqID }, taskRespData));
|
170
180
|
}
|
171
181
|
}
|
172
182
|
} catch (ex) {
|
173
|
-
|
183
|
+
logger(`mqtt message parse error: ${ex && ex.message ? ex.message : ex}`, "error");
|
174
184
|
}
|
175
185
|
});
|
186
|
+
|
176
187
|
mqttClient.on("close", function () { });
|
177
188
|
mqttClient.on("disconnect", () => { });
|
178
189
|
};
|
@@ -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;
|