nodejs-insta-private-api-mqt 1.3.92 → 1.4.1
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.
|
@@ -1036,8 +1036,48 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
1036
1036
|
// actually connect the mqtt client (this will emit connect when done)
|
|
1037
1037
|
await this._mqtt.connect();
|
|
1038
1038
|
// Commands uses mqtt client; Commands.updateSubscriptions has been set to use qos 0.
|
|
1039
|
-
|
|
1040
|
-
|
|
1039
|
+
// Commands uses mqtt client; wrap commands to ensure online + serialize calls (prevents "idle-dead" send failures)
|
|
1040
|
+
|
|
1041
|
+
this._rawCommands = new commands_1.Commands(this._mqtt);
|
|
1042
|
+
|
|
1043
|
+
// Simple promise-chain queue to avoid concurrent publishes racing during reconnects
|
|
1044
|
+
|
|
1045
|
+
this._cmdQueue = this._cmdQueue || Promise.resolve();
|
|
1046
|
+
|
|
1047
|
+
const runQueued = (fn) => {
|
|
1048
|
+
|
|
1049
|
+
const task = async () => {
|
|
1050
|
+
|
|
1051
|
+
await this.ensureOnline(); // ping/reconnect if needed
|
|
1052
|
+
|
|
1053
|
+
return await fn();
|
|
1054
|
+
|
|
1055
|
+
};
|
|
1056
|
+
|
|
1057
|
+
// keep the queue alive even if a task fails
|
|
1058
|
+
|
|
1059
|
+
this._cmdQueue = this._cmdQueue.then(task, task);
|
|
1060
|
+
|
|
1061
|
+
return this._cmdQueue;
|
|
1062
|
+
|
|
1063
|
+
};
|
|
1064
|
+
|
|
1065
|
+
// Proxy: every command method becomes queued + guarded
|
|
1066
|
+
|
|
1067
|
+
this.commands = new Proxy(this._rawCommands, {
|
|
1068
|
+
|
|
1069
|
+
get: (target, prop) => {
|
|
1070
|
+
|
|
1071
|
+
const value = target[prop];
|
|
1072
|
+
|
|
1073
|
+
if (typeof value !== 'function') return value;
|
|
1074
|
+
|
|
1075
|
+
return (...args) => runQueued(() => value.apply(target, args));
|
|
1076
|
+
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
});
|
|
1080
|
+
// Notify higher-level code that we are connected
|
|
1041
1081
|
this._mqttConnected = true;
|
|
1042
1082
|
this._connectInProgress = false;
|
|
1043
1083
|
this.emit('connected');
|
|
@@ -1057,11 +1097,12 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
1057
1097
|
this.on('iris', updateLast);
|
|
1058
1098
|
const KEEPALIVE_FOREGROUND_MS = (this.initOptions && this.initOptions.keepaliveForegroundMs) ? this.initOptions.keepaliveForegroundMs : 45000; // 45s keepalive pulse
|
|
1059
1099
|
const MESSAGE_SYNC_REFRESH_MS = (this.initOptions && this.initOptions.messageSyncRefreshMs) ? this.initOptions.messageSyncRefreshMs : 300000;
|
|
1060
|
-
const TRAFFIC_INACTIVITY_MS = (this.initOptions && this.initOptions.trafficInactivityMs) ? this.initOptions.trafficInactivityMs :
|
|
1100
|
+
const TRAFFIC_INACTIVITY_MS = (this.initOptions && this.initOptions.trafficInactivityMs) ? this.initOptions.trafficInactivityMs : 120000; // 2 min default for faster recovery
|
|
1061
1101
|
const HEARTBEAT_MS = (this.initOptions && this.initOptions.heartbeatMs) ? this.initOptions.heartbeatMs : 90000; // 90s mobile-like heartbeat
|
|
1062
1102
|
try {
|
|
1063
1103
|
if (this._foregroundTimer)
|
|
1064
1104
|
clearInterval(this._foregroundTimer);
|
|
1105
|
+
this._foregroundConsecutiveFailures = 0;
|
|
1065
1106
|
this._foregroundTimer = setInterval(async () => {
|
|
1066
1107
|
try {
|
|
1067
1108
|
if (!this.commands)
|
|
@@ -1071,10 +1112,18 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
1071
1112
|
data: { foreground: true }
|
|
1072
1113
|
});
|
|
1073
1114
|
this._lastMessageAt = Date.now();
|
|
1115
|
+
this._foregroundConsecutiveFailures = 0;
|
|
1074
1116
|
this.realtimeDebug('[KEEPALIVE] Foreground pulse sent.');
|
|
1075
1117
|
}
|
|
1076
1118
|
catch (e) {
|
|
1077
|
-
this.
|
|
1119
|
+
this._foregroundConsecutiveFailures = (this._foregroundConsecutiveFailures || 0) + 1;
|
|
1120
|
+
this.realtimeDebug(`[KEEPALIVE] Foreground pulse failed (${this._foregroundConsecutiveFailures}):`, e?.message || e);
|
|
1121
|
+
if (this._foregroundConsecutiveFailures >= 3 && !this._reconnectInProgress && !this.safeDisconnect) {
|
|
1122
|
+
this.realtimeDebug('[KEEPALIVE] 3 consecutive foreground failures - connection likely dead, triggering reconnect');
|
|
1123
|
+
this._mqttConnected = false;
|
|
1124
|
+
this._foregroundConsecutiveFailures = 0;
|
|
1125
|
+
try { await this._attemptReconnectSafely(e); } catch(re) {}
|
|
1126
|
+
}
|
|
1078
1127
|
}
|
|
1079
1128
|
}, KEEPALIVE_FOREGROUND_MS + Math.floor(Math.random() * 5000));
|
|
1080
1129
|
}
|
|
@@ -1113,6 +1162,7 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
1113
1162
|
if (this._mqtt && typeof this._mqtt.ping === 'function') {
|
|
1114
1163
|
await this._mqtt.ping();
|
|
1115
1164
|
this._lastMessageAt = Date.now();
|
|
1165
|
+
this._lastServerTrafficAt = Date.now();
|
|
1116
1166
|
this.realtimeDebug('[WATCHDOG] Ping succeeded, connection is alive.');
|
|
1117
1167
|
return;
|
|
1118
1168
|
}
|
|
@@ -1140,10 +1190,16 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
1140
1190
|
try {
|
|
1141
1191
|
await this._mqtt.ping();
|
|
1142
1192
|
this._lastMessageAt = Date.now();
|
|
1193
|
+
this._lastServerTrafficAt = Date.now();
|
|
1143
1194
|
this.realtimeDebug('[HEARTBEAT] mqtt.ping() ok.');
|
|
1144
1195
|
}
|
|
1145
1196
|
catch (e) {
|
|
1146
1197
|
this.realtimeDebug('[HEARTBEAT] mqtt.ping failed:', e?.message || e);
|
|
1198
|
+
if (!this._reconnectInProgress && !this.safeDisconnect) {
|
|
1199
|
+
this.realtimeDebug('[HEARTBEAT] Ping failed - triggering reconnect');
|
|
1200
|
+
this._mqttConnected = false;
|
|
1201
|
+
try { await this._attemptReconnectSafely(e); } catch(re) {}
|
|
1202
|
+
}
|
|
1147
1203
|
}
|
|
1148
1204
|
}
|
|
1149
1205
|
}
|
|
@@ -1155,6 +1211,33 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
1155
1211
|
catch (e) {
|
|
1156
1212
|
this.realtimeDebug('[HEARTBEAT] Could not start heartbeat timer:', e?.message || e);
|
|
1157
1213
|
}
|
|
1214
|
+
try {
|
|
1215
|
+
if (this._reconnectSafetyNet)
|
|
1216
|
+
clearInterval(this._reconnectSafetyNet);
|
|
1217
|
+
this._reconnectSafetyNet = setInterval(async () => {
|
|
1218
|
+
try {
|
|
1219
|
+
if (this._reconnectInProgress && this._reconnectStartedAt) {
|
|
1220
|
+
const stuckMs = Date.now() - this._reconnectStartedAt;
|
|
1221
|
+
if (stuckMs > 10 * 60 * 1000) {
|
|
1222
|
+
this.realtimeDebug('[SAFETY_NET] _reconnectInProgress stuck for >10min — force resetting and retrying');
|
|
1223
|
+
this._reconnectInProgress = false;
|
|
1224
|
+
this._reconnectStartedAt = null;
|
|
1225
|
+
try { await this._attemptReconnectSafely(); } catch(e) {}
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
if (!this._mqttConnected && !this._reconnectInProgress && !this.safeDisconnect && !this._connectInProgress) {
|
|
1229
|
+
this.realtimeDebug('[SAFETY_NET] Not connected and no reconnect in progress — triggering reconnect');
|
|
1230
|
+
try { await this._attemptReconnectSafely(); } catch(e) {}
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
catch (e) {
|
|
1234
|
+
this.realtimeDebug('[SAFETY_NET] fault:', e?.message || e);
|
|
1235
|
+
}
|
|
1236
|
+
}, 2 * 60 * 1000);
|
|
1237
|
+
}
|
|
1238
|
+
catch (e) {
|
|
1239
|
+
this.realtimeDebug('[SAFETY_NET] Could not start safety net timer:', e?.message || e);
|
|
1240
|
+
}
|
|
1158
1241
|
}
|
|
1159
1242
|
catch (e) {
|
|
1160
1243
|
this.realtimeDebug('[WATCHDOG] initialization error:', e?.message || e);
|
|
@@ -1558,18 +1641,33 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
1558
1641
|
*/
|
|
1559
1642
|
async _attemptReconnectSafely(lastError) {
|
|
1560
1643
|
if (this._reconnectInProgress) {
|
|
1561
|
-
|
|
1562
|
-
|
|
1644
|
+
const stuckMs = Date.now() - (this._reconnectStartedAt || Date.now());
|
|
1645
|
+
if (stuckMs > 5 * 60 * 1000) {
|
|
1646
|
+
this.realtimeDebug('[RECONNECT] _reconnectInProgress stuck for >5min — resetting flag');
|
|
1647
|
+
this._reconnectInProgress = false;
|
|
1648
|
+
} else {
|
|
1649
|
+
this.realtimeDebug('[RECONNECT] Skipped — reconnect already in progress');
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1563
1652
|
}
|
|
1564
1653
|
if (this._mqttConnected) {
|
|
1565
|
-
|
|
1566
|
-
|
|
1654
|
+
try {
|
|
1655
|
+
if (this._mqtt && typeof this._mqtt.ping === 'function') {
|
|
1656
|
+
await this._mqtt.ping();
|
|
1657
|
+
this.realtimeDebug('[RECONNECT] Skipped — MQTT is already connected and ping succeeded');
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
} catch(e) {
|
|
1661
|
+
this.realtimeDebug('[RECONNECT] MQTT marked connected but ping failed — proceeding with reconnect');
|
|
1662
|
+
this._mqttConnected = false;
|
|
1663
|
+
}
|
|
1567
1664
|
}
|
|
1568
1665
|
if (this.safeDisconnect) {
|
|
1569
1666
|
this.realtimeDebug('[RECONNECT] Skipped — safe disconnect active');
|
|
1570
1667
|
return;
|
|
1571
1668
|
}
|
|
1572
1669
|
this._reconnectInProgress = true;
|
|
1670
|
+
this._reconnectStartedAt = Date.now();
|
|
1573
1671
|
if (this._reconnectTimeoutId) {
|
|
1574
1672
|
clearTimeout(this._reconnectTimeoutId);
|
|
1575
1673
|
this._reconnectTimeoutId = null;
|
|
@@ -1581,7 +1679,7 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
1581
1679
|
}
|
|
1582
1680
|
try {
|
|
1583
1681
|
let attempt = 0;
|
|
1584
|
-
const maxAttempts =
|
|
1682
|
+
const maxAttempts = 999;
|
|
1585
1683
|
let lastErrorType = lastError ? (this.errorHandler ? this.errorHandler.classifyError(lastError) : 'unknown') : 'unknown';
|
|
1586
1684
|
while (attempt < maxAttempts) {
|
|
1587
1685
|
attempt++;
|
|
@@ -1960,6 +2058,11 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
1960
2058
|
clearInterval(this._activeKeepaliveTimer);
|
|
1961
2059
|
this._activeKeepaliveTimer = null;
|
|
1962
2060
|
}
|
|
2061
|
+
// Clear reconnect safety net
|
|
2062
|
+
if (this._reconnectSafetyNet) {
|
|
2063
|
+
clearInterval(this._reconnectSafetyNet);
|
|
2064
|
+
this._reconnectSafetyNet = null;
|
|
2065
|
+
}
|
|
1963
2066
|
// clear credential refresh timer
|
|
1964
2067
|
if (this._mqttAuthRefreshTimer) {
|
|
1965
2068
|
try {
|
|
@@ -2102,5 +2205,80 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
2102
2205
|
this.realtimeDebug('[ACTIVE_QUERY] could not start keepalive timer:', e?.message || e);
|
|
2103
2206
|
}
|
|
2104
2207
|
}
|
|
2208
|
+
/**
|
|
2209
|
+
* ensureOnline()
|
|
2210
|
+
* - fixes the classic "idle -> socket half-dead -> sends fail" issue
|
|
2211
|
+
* - pings the broker; if ping fails/timeouts, triggers a safe reconnect and waits until connected
|
|
2212
|
+
*/
|
|
2213
|
+
async ensureOnline(options = {}) {
|
|
2214
|
+
const timeoutMs = typeof options.timeoutMs === 'number' ? options.timeoutMs : 15000;
|
|
2215
|
+
const pingTimeoutMs = typeof options.pingTimeoutMs === 'number' ? options.pingTimeoutMs : 7000;
|
|
2216
|
+
if (this.safeDisconnect) {
|
|
2217
|
+
throw new Error('RealtimeClient is in safeDisconnect state');
|
|
2218
|
+
}
|
|
2219
|
+
// If a connect/reconnect is already happening, just wait for it
|
|
2220
|
+
if (this._connectInProgress || this._reconnectInProgress) {
|
|
2221
|
+
await this._waitForConnected(timeoutMs);
|
|
2222
|
+
return true;
|
|
2223
|
+
}
|
|
2224
|
+
// If we are not connected, reconnect and wait
|
|
2225
|
+
if (!this._mqtt || !this._mqttConnected) {
|
|
2226
|
+
try {
|
|
2227
|
+
await this._attemptReconnectSafely();
|
|
2228
|
+
} catch (e) { /* ignore */ }
|
|
2229
|
+
await this._waitForConnected(timeoutMs);
|
|
2230
|
+
return true;
|
|
2231
|
+
}
|
|
2232
|
+
// If we are connected, do a lightweight ping to detect half-dead sockets
|
|
2233
|
+
if (this._mqtt && typeof this._mqtt.ping === 'function') {
|
|
2234
|
+
try {
|
|
2235
|
+
await Promise.race([
|
|
2236
|
+
this._mqtt.ping(),
|
|
2237
|
+
this._delay(pingTimeoutMs).then(() => { throw new Error('mqtt.ping timeout'); })
|
|
2238
|
+
]);
|
|
2239
|
+
this._lastMessageAt = Date.now();
|
|
2240
|
+
this._lastServerTrafficAt = Date.now();
|
|
2241
|
+
return true;
|
|
2242
|
+
} catch (e) {
|
|
2243
|
+
// Ping failed -> force reconnect
|
|
2244
|
+
this.realtimeDebug('[ensureOnline] ping failed -> reconnect', e?.message || e);
|
|
2245
|
+
this._mqttConnected = false;
|
|
2246
|
+
try { await this._attemptReconnectSafely(e); } catch (re) { /* ignore */ }
|
|
2247
|
+
await this._waitForConnected(timeoutMs);
|
|
2248
|
+
return true;
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
return true;
|
|
2252
|
+
}
|
|
2253
|
+
_delay(ms) {
|
|
2254
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2255
|
+
}
|
|
2256
|
+
_waitForConnected(timeoutMs) {
|
|
2257
|
+
return new Promise((resolve, reject) => {
|
|
2258
|
+
if (this._mqttConnected) return resolve(true);
|
|
2259
|
+
let done = false;
|
|
2260
|
+
const finishOk = () => {
|
|
2261
|
+
if (done) return;
|
|
2262
|
+
done = true;
|
|
2263
|
+
cleanup();
|
|
2264
|
+
resolve(true);
|
|
2265
|
+
};
|
|
2266
|
+
const finishErr = (err) => {
|
|
2267
|
+
if (done) return;
|
|
2268
|
+
done = true;
|
|
2269
|
+
cleanup();
|
|
2270
|
+
reject(err);
|
|
2271
|
+
};
|
|
2272
|
+
const timer = setTimeout(() => finishErr(new Error('Timed out waiting for MQTT connection')), timeoutMs);
|
|
2273
|
+
const cleanup = () => {
|
|
2274
|
+
try { clearTimeout(timer); } catch (_) { }
|
|
2275
|
+
try { this.off('connected', finishOk); } catch (_) { }
|
|
2276
|
+
try { this.off('mqtt_connected', finishOk); } catch (_) { }
|
|
2277
|
+
};
|
|
2278
|
+
try { this.on('connected', finishOk); } catch (_) { }
|
|
2279
|
+
try { this.on('mqtt_connected', finishOk); } catch (_) { }
|
|
2280
|
+
});
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2105
2283
|
}
|
|
2106
2284
|
exports.RealtimeClient = RealtimeClient;
|