nodejs-insta-private-api-mqt 1.3.93 → 1.4.2

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.
@@ -61,7 +61,7 @@ class MQTToTClient extends mqtts_1.MqttClient {
61
61
  // Register listeners (errors, disconnect, pingresps, etc.)
62
62
  this.registerListeners();
63
63
  this.requirePayload = options.requirePayload;
64
- this._keepaliveMs = (typeof options.keepaliveMs === 'number') ? options.keepaliveMs : (45 * 1000);
64
+ this._keepaliveMs = (typeof options.keepaliveMs === 'number') ? options.keepaliveMs : (8 * 60 * 1000);
65
65
  this._consecutivePingFailures = 0;
66
66
  this._startKeepalive();
67
67
  }
@@ -69,7 +69,7 @@ class MQTToTClient extends mqtts_1.MqttClient {
69
69
  try {
70
70
  if (this._keepaliveTimer)
71
71
  clearInterval(this._keepaliveTimer);
72
- const jitter = Math.floor(Math.random() * 5000);
72
+ const jitter = Math.floor(Math.random() * 30000);
73
73
  this._keepaliveTimer = setInterval(() => {
74
74
  try {
75
75
  if (typeof this.ping === 'function') {
@@ -147,7 +147,7 @@ class MQTToTClient extends mqtts_1.MqttClient {
147
147
  return;
148
148
  }
149
149
  let delay = 3000 + Math.floor(Math.random() * 2000);
150
- const maxAttempts = 999;
150
+ const maxAttempts = 12;
151
151
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
152
152
  try {
153
153
  this.mqttotDebug(`Reconnect attempt #${attempt + 1} (delay ${delay}ms)`);
@@ -5,6 +5,58 @@ const constants_1 = require("../../constants");
5
5
  const shared_1 = require("../../shared");
6
6
  const Chance = require("chance");
7
7
  const thrift_1 = require("../../thrift");
8
+
9
+ /**
10
+ * SerialQueue — Coadă strict serială cu retry și exponential backoff.
11
+ * Folosită per-thread pentru a serializa operațiile MQTT și a preveni
12
+ * race conditions și mesaje pierdute la reconectare.
13
+ */
14
+ class SerialQueue {
15
+ constructor(name) {
16
+ this.name = name;
17
+ this.queue = [];
18
+ this.running = false;
19
+ }
20
+ enqueue(label, fn) {
21
+ return new Promise((resolve, reject) => {
22
+ this.queue.push({ label, fn, resolve, reject, retries: 0 });
23
+ this._tick();
24
+ });
25
+ }
26
+ _tick() {
27
+ if (this.running || this.queue.length === 0) return;
28
+ this.running = true;
29
+ this._processNext();
30
+ }
31
+ async _processNext() {
32
+ const MAX_RETRIES = 3;
33
+ const BASE_DELAY_MS = 500;
34
+ const item = this.queue[0];
35
+ if (!item) { this.running = false; return; }
36
+ try {
37
+ const result = await item.fn();
38
+ this.queue.shift();
39
+ item.resolve(result);
40
+ } catch (err) {
41
+ if (item.retries < MAX_RETRIES) {
42
+ item.retries++;
43
+ const delay = BASE_DELAY_MS * Math.pow(2, item.retries - 1);
44
+ console.warn(`[Queue:${this.name}] "${item.label}" failed (try ${item.retries}/${MAX_RETRIES}), retry in ${delay}ms:`, err && err.message || err);
45
+ await new Promise(r => setTimeout(r, delay));
46
+ } else {
47
+ console.error(`[Queue:${this.name}] "${item.label}" failed after ${MAX_RETRIES} retries:`, err && err.message || err);
48
+ this.queue.shift();
49
+ item.reject(err);
50
+ }
51
+ }
52
+ if (this.queue.length > 0) {
53
+ this._processNext();
54
+ } else {
55
+ this.running = false;
56
+ }
57
+ }
58
+ }
59
+
8
60
  class DirectCommands {
9
61
  constructor(client) {
10
62
  this.directDebug = (0, shared_1.debugChannel)('realtime', 'direct');
@@ -20,23 +72,38 @@ class DirectCommands {
20
72
  ];
21
73
  this.client = client;
22
74
  this.chance = new Chance();
75
+ // Coadă per-thread pentru operații MQTT
76
+ this._threadQueues = new Map();
77
+ // Coadă globală pentru operații non-thread (ex: foreground state)
78
+ this._globalQueue = new SerialQueue('direct:global');
79
+ }
80
+
81
+ /** Returnează (sau creează) coada pentru un threadId specific */
82
+ _getThreadQueue(threadId) {
83
+ if (!this._threadQueues.has(threadId)) {
84
+ this._threadQueues.set(threadId, new SerialQueue(`direct:${threadId}`));
85
+ }
86
+ return this._threadQueues.get(threadId);
23
87
  }
88
+
24
89
  async sendForegroundState(state) {
25
90
  this.directDebug(`Updated foreground state: ${JSON.stringify(state)}`);
26
- return this.client
27
- .publish({
28
- topic: constants_1.Topics.FOREGROUND_STATE.id,
29
- payload: await (0, shared_1.compressDeflate)(Buffer.concat([Buffer.alloc(1, 0), (0, thrift_1.thriftWriteFromObject)(state, this.foregroundStateConfig)])),
30
- qosLevel: 1,
31
- })
32
- .then(res => {
33
- // updating the keepAlive to match the shared value
34
- if ((0, shared_1.notUndefined)(state.keepAliveTimeout)) {
35
- this.client.keepAlive = state.keepAliveTimeout;
36
- }
37
- return res;
38
- });
39
- }
91
+ return this._globalQueue.enqueue('sendForegroundState', async () => {
92
+ return this.client
93
+ .publish({
94
+ topic: constants_1.Topics.FOREGROUND_STATE.id,
95
+ payload: await (0, shared_1.compressDeflate)(Buffer.concat([Buffer.alloc(1, 0), (0, thrift_1.thriftWriteFromObject)(state, this.foregroundStateConfig)])),
96
+ qosLevel: 1,
97
+ })
98
+ .then(res => {
99
+ if ((0, shared_1.notUndefined)(state.keepAliveTimeout)) {
100
+ this.client.keepAlive = state.keepAliveTimeout;
101
+ }
102
+ return res;
103
+ });
104
+ });
105
+ }
106
+
40
107
  async sendCommand({ action, data, threadId, clientContext, }) {
41
108
  if (clientContext) {
42
109
  data.client_context = clientContext;
@@ -46,12 +113,16 @@ class DirectCommands {
46
113
  thread_id: threadId,
47
114
  ...data,
48
115
  });
49
- return this.client.publish({
50
- topic: constants_1.Topics.SEND_MESSAGE.id,
51
- qosLevel: 1,
52
- payload: await (0, shared_1.compressDeflate)(json),
116
+ const q = this._getThreadQueue(threadId);
117
+ return q.enqueue(action, async () => {
118
+ return this.client.publish({
119
+ topic: constants_1.Topics.SEND_MESSAGE.id,
120
+ qosLevel: 1,
121
+ payload: await (0, shared_1.compressDeflate)(json),
122
+ });
53
123
  });
54
124
  }
125
+
55
126
  async sendItem({ threadId, itemType, data, clientContext }) {
56
127
  return this.sendCommand({
57
128
  action: 'send_item',
@@ -5,6 +5,57 @@ const shared_1 = require("../../shared");
5
5
  const uuid_1 = require("uuid");
6
6
  const constants_1 = require("../../constants");
7
7
  const thrift_1 = require("../../thrift");
8
+
9
+ /**
10
+ * SerialQueue — Coadă strict serială cu retry și exponential backoff.
11
+ * Folosită per-thread pentru a serializa operațiile MQTT și a preveni
12
+ * race conditions și mesaje pierdute la reconectare.
13
+ */
14
+ class SerialQueue {
15
+ constructor(name) {
16
+ this.name = name;
17
+ this.queue = [];
18
+ this.running = false;
19
+ }
20
+ enqueue(label, fn) {
21
+ return new Promise((resolve, reject) => {
22
+ this.queue.push({ label, fn, resolve, reject, retries: 0 });
23
+ this._tick();
24
+ });
25
+ }
26
+ _tick() {
27
+ if (this.running || this.queue.length === 0) return;
28
+ this.running = true;
29
+ this._processNext();
30
+ }
31
+ async _processNext() {
32
+ const MAX_RETRIES = 3;
33
+ const BASE_DELAY_MS = 500;
34
+ const item = this.queue[0];
35
+ if (!item) { this.running = false; return; }
36
+ try {
37
+ const result = await item.fn();
38
+ this.queue.shift();
39
+ item.resolve(result);
40
+ } catch (err) {
41
+ if (item.retries < MAX_RETRIES) {
42
+ item.retries++;
43
+ const delay = BASE_DELAY_MS * Math.pow(2, item.retries - 1);
44
+ console.warn(`[Queue:${this.name}] "${item.label}" failed (try ${item.retries}/${MAX_RETRIES}), retry in ${delay}ms:`, err && err.message || err);
45
+ await new Promise(r => setTimeout(r, delay));
46
+ } else {
47
+ console.error(`[Queue:${this.name}] "${item.label}" failed after ${MAX_RETRIES} retries:`, err && err.message || err);
48
+ this.queue.shift();
49
+ item.reject(err);
50
+ }
51
+ }
52
+ if (this.queue.length > 0) {
53
+ this._processNext();
54
+ } else {
55
+ this.running = false;
56
+ }
57
+ }
58
+ }
8
59
  /**
9
60
  * EnhancedDirectCommands
10
61
  *
@@ -32,6 +83,17 @@ class EnhancedDirectCommands {
32
83
  thrift_1.ThriftDescriptors.listOfBinary('unsubscribeGenericTopics', 7),
33
84
  thrift_1.ThriftDescriptors.int64('requestId', 8),
34
85
  ];
86
+ // Coadă per-thread pentru operații MQTT
87
+ this._threadQueues = new Map();
88
+ // Coadă globală pentru operații non-thread (ex: foreground state)
89
+ this._globalQueue = new SerialQueue('enhanced:global');
90
+ }
91
+ /** Returnează (sau creează) coada pentru un threadId specific */
92
+ _getThreadQueue(threadId) {
93
+ if (!this._threadQueues.has(threadId)) {
94
+ this._threadQueues.set(threadId, new SerialQueue(`enhanced:${threadId}`));
95
+ }
96
+ return this._threadQueues.get(threadId);
35
97
  }
36
98
  /**
37
99
  * Attempt to locate the MQTT client object on the realtime client.
@@ -132,62 +194,66 @@ class EnhancedDirectCommands {
132
194
  }
133
195
  /**
134
196
  * Send foreground state via MQTT with Thrift encoding (matching instagram_mqtt)
197
+ * Folosește coada globală — nu depinde de un thread specific.
135
198
  */
136
199
  async sendForegroundState(state) {
137
200
  this.enhancedDebug(`Updated foreground state: ${JSON.stringify(state)}`);
138
- try {
139
- const mqtt = this.getMqtt();
140
- const thriftBuffer = (0, thrift_1.thriftWriteFromObject)(state, this.foregroundStateConfig);
141
- const concat = Buffer.concat([
142
- Buffer.alloc(1, 0),
143
- thriftBuffer
144
- ]);
145
- // ensure we pass Buffer to compressDeflate
146
- const payload = await (0, shared_1.compressDeflate)(concat);
147
- const result = await this.publishToMqtt(mqtt, {
148
- topic: constants_1.Topics.FOREGROUND_STATE.id,
149
- payload: payload,
150
- qosLevel: 1,
151
- });
152
- // Update keepAlive if provided
153
- if ((0, shared_1.notUndefined)(state.keepAliveTimeout)) {
154
- mqtt.keepAlive = state.keepAliveTimeout;
201
+ return this._globalQueue.enqueue('sendForegroundState', async () => {
202
+ try {
203
+ const mqtt = this.getMqtt();
204
+ const thriftBuffer = (0, thrift_1.thriftWriteFromObject)(state, this.foregroundStateConfig);
205
+ const concat = Buffer.concat([
206
+ Buffer.alloc(1, 0),
207
+ thriftBuffer
208
+ ]);
209
+ const payload = await (0, shared_1.compressDeflate)(concat);
210
+ const result = await this.publishToMqtt(mqtt, {
211
+ topic: constants_1.Topics.FOREGROUND_STATE.id,
212
+ payload: payload,
213
+ qosLevel: 1,
214
+ });
215
+ if ((0, shared_1.notUndefined)(state.keepAliveTimeout)) {
216
+ mqtt.keepAlive = state.keepAliveTimeout;
217
+ }
218
+ this.enhancedDebug(`✅ Foreground state updated via MQTT!`);
219
+ return result;
155
220
  }
156
- this.enhancedDebug(`✅ Foreground state updated via MQTT!`);
157
- return result;
158
- }
159
- catch (err) {
160
- this.enhancedDebug(`Foreground state failed: ${err && err.message ? err.message : String(err)}`);
161
- throw err;
162
- }
221
+ catch (err) {
222
+ this.enhancedDebug(`Foreground state failed: ${err && err.message ? err.message : String(err)}`);
223
+ throw err;
224
+ }
225
+ });
163
226
  }
164
227
  /**
165
228
  * Base command sender (matching instagram_mqtt format)
166
229
  * It encodes the command as JSON, compresses, and publishes to SEND_MESSAGE topic.
230
+ * All operations are serialized per-thread via SerialQueue.
167
231
  */
168
232
  async sendCommand({ action, data, threadId, clientContext }) {
169
- try {
170
- const mqtt = this.getMqtt();
171
- if (clientContext) {
172
- data.client_context = clientContext;
173
- }
174
- const json = JSON.stringify({
175
- action,
176
- thread_id: threadId,
177
- ...data,
178
- });
179
- // ensure Buffer (some compress implementations expect Buffer)
180
- const payload = await (0, shared_1.compressDeflate)(Buffer.from(json));
181
- return this.publishToMqtt(mqtt, {
182
- topic: constants_1.Topics.SEND_MESSAGE.id,
183
- qosLevel: 1,
184
- payload: payload,
185
- });
186
- }
187
- catch (err) {
188
- this.enhancedDebug(`sendCommand failed: ${err && err.message ? err.message : String(err)}`);
189
- throw err;
233
+ if (clientContext) {
234
+ data.client_context = clientContext;
190
235
  }
236
+ const json = JSON.stringify({
237
+ action,
238
+ thread_id: threadId,
239
+ ...data,
240
+ });
241
+ const q = this._getThreadQueue(threadId);
242
+ return q.enqueue(action, async () => {
243
+ try {
244
+ const mqtt = this.getMqtt();
245
+ const payload = await (0, shared_1.compressDeflate)(Buffer.from(json));
246
+ return this.publishToMqtt(mqtt, {
247
+ topic: constants_1.Topics.SEND_MESSAGE.id,
248
+ qosLevel: 1,
249
+ payload: payload,
250
+ });
251
+ }
252
+ catch (err) {
253
+ this.enhancedDebug(`sendCommand failed: ${err && err.message ? err.message : String(err)}`);
254
+ throw err;
255
+ }
256
+ });
191
257
  }
192
258
  /**
193
259
  * Base item sender (matching instagram_mqtt format)
@@ -3,14 +3,78 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DMSender = void 0;
4
4
  const shared_1 = require("../../shared");
5
5
  const uuid_1 = require("uuid");
6
+
7
+ /**
8
+ *SerialQueue — Strictly serial queue with retry and exponential backoff.
9
+ * Used per-thread to serialize MQTT operations and prevent
10
+ * Race conditions and messages lost when reconnecting.
11
+ */
12
+ class SerialQueue {
13
+ constructor(name) {
14
+ this.name = name;
15
+ this.queue = [];
16
+ this.running = false;
17
+ }
18
+ enqueue(label, fn) {
19
+ return new Promise((resolve, reject) => {
20
+ this.queue.push({ label, fn, resolve, reject, retries: 0 });
21
+ this._tick();
22
+ });
23
+ }
24
+ _tick() {
25
+ if (this.running || this.queue.length === 0) return;
26
+ this.running = true;
27
+ this._processNext();
28
+ }
29
+ async _processNext() {
30
+ const MAX_RETRIES = 3;
31
+ const BASE_DELAY_MS = 500;
32
+ const item = this.queue[0];
33
+ if (!item) { this.running = false; return; }
34
+ try {
35
+ const result = await item.fn();
36
+ this.queue.shift();
37
+ item.resolve(result);
38
+ } catch (err) {
39
+ if (item.retries < MAX_RETRIES) {
40
+ item.retries++;
41
+ const delay = BASE_DELAY_MS * Math.pow(2, item.retries - 1);
42
+ console.warn(`[Queue:${this.name}] "${item.label}" failed (try ${item.retries}/${MAX_RETRIES}), retry in ${delay}ms:`, err && err.message || err);
43
+ await new Promise(r => setTimeout(r, delay));
44
+ } else {
45
+ console.error(`[Queue:${this.name}] "${item.label}" failed after ${MAX_RETRIES} retries:`, err && err.message || err);
46
+ this.queue.shift();
47
+ item.reject(err);
48
+ }
49
+ }
50
+ if (this.queue.length > 0) {
51
+ this._processNext();
52
+ } else {
53
+ this.running = false;
54
+ }
55
+ }
56
+ }
57
+
6
58
  /**
7
59
  * Direct Message Sender via MQTT
60
+ * All operations are serialized per-thread via SerialQueue.
8
61
  */
9
62
  class DMSender {
10
63
  constructor(client) {
11
64
  this.dmDebug = (0, shared_1.debugChannel)('realtime', 'dm-sender');
12
65
  this.client = client;
66
+ //Per-thread queue for DM operations
67
+ this._threadQueues = new Map();
13
68
  }
69
+
70
+ /** Returns (or creates) the queue for a specific threadId */
71
+ _getThreadQueue(threadId) {
72
+ if (!this._threadQueues.has(threadId)) {
73
+ this._threadQueues.set(threadId, new SerialQueue(`dm-sender:${threadId}`));
74
+ }
75
+ return this._threadQueues.get(threadId);
76
+ }
77
+
14
78
  /**
15
79
  * Send text message via MQTT
16
80
  */
@@ -24,18 +88,22 @@ class DMSender {
24
88
  timestamp: Date.now(),
25
89
  client_context: clientContext || (0, uuid_1.v4)(),
26
90
  };
27
- try {
28
- return await this.client.directCommands?.sendCommand({
29
- action: 'send_item',
30
- data: command,
31
- threadId,
32
- });
33
- }
34
- catch (err) {
35
- this.dmDebug(`Failed to send message: ${err.message}`);
36
- throw err;
37
- }
91
+ const q = this._getThreadQueue(threadId);
92
+ return q.enqueue('sendTextMessage', async () => {
93
+ try {
94
+ return await this.client.directCommands?.sendCommand({
95
+ action: 'send_item',
96
+ data: command,
97
+ threadId,
98
+ });
99
+ }
100
+ catch (err) {
101
+ this.dmDebug(`Failed to send message: ${err.message}`);
102
+ throw err;
103
+ }
104
+ });
38
105
  }
106
+
39
107
  /**
40
108
  * Send media message via MQTT (photo/video)
41
109
  */
@@ -49,18 +117,22 @@ class DMSender {
49
117
  timestamp: Date.now(),
50
118
  client_context: clientContext || (0, uuid_1.v4)(),
51
119
  };
52
- try {
53
- return await this.client.directCommands?.sendCommand({
54
- action: 'send_item',
55
- data: command,
56
- threadId,
57
- });
58
- }
59
- catch (err) {
60
- this.dmDebug(`Failed to send media: ${err.message}`);
61
- throw err;
62
- }
120
+ const q = this._getThreadQueue(threadId);
121
+ return q.enqueue('sendMediaMessage', async () => {
122
+ try {
123
+ return await this.client.directCommands?.sendCommand({
124
+ action: 'send_item',
125
+ data: command,
126
+ threadId,
127
+ });
128
+ }
129
+ catch (err) {
130
+ this.dmDebug(`Failed to send media: ${err.message}`);
131
+ throw err;
132
+ }
133
+ });
63
134
  }
135
+
64
136
  /**
65
137
  * Send link message
66
138
  */
@@ -75,17 +147,20 @@ class DMSender {
75
147
  timestamp: Date.now(),
76
148
  client_context: clientContext || (0, uuid_1.v4)(),
77
149
  };
78
- try {
79
- return await this.client.directCommands?.sendCommand({
80
- action: 'send_item',
81
- data: command,
82
- threadId,
83
- });
84
- }
85
- catch (err) {
86
- this.dmDebug(`Failed to send link: ${err.message}`);
87
- throw err;
88
- }
150
+ const q = this._getThreadQueue(threadId);
151
+ return q.enqueue('sendLinkMessage', async () => {
152
+ try {
153
+ return await this.client.directCommands?.sendCommand({
154
+ action: 'send_item',
155
+ data: command,
156
+ threadId,
157
+ });
158
+ }
159
+ catch (err) {
160
+ this.dmDebug(`Failed to send link: ${err.message}`);
161
+ throw err;
162
+ }
163
+ });
89
164
  }
90
165
  }
91
166
  exports.DMSender = DMSender;
@@ -30,7 +30,7 @@ class ErrorHandler {
30
30
  constructor(client) {
31
31
  this.errorDebug = (0, shared_1.debugChannel)('realtime', 'errors');
32
32
  this.errorCount = 0;
33
- this.maxRetries = 999;
33
+ this.maxRetries = 15;
34
34
  this.client = client;
35
35
  this.errorHistory = [];
36
36
  this.rateLimitUntil = 0;
@@ -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
- this.commands = new commands_1.Commands(this._mqtt);
1040
- // Notify higher-level code that we are connected
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');
@@ -2165,5 +2205,80 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
2165
2205
  this.realtimeDebug('[ACTIVE_QUERY] could not start keepalive timer:', e?.message || e);
2166
2206
  }
2167
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
+
2168
2283
  }
2169
2284
  exports.RealtimeClient = RealtimeClient;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodejs-insta-private-api-mqt",
3
- "version": "1.3.93",
3
+ "version": "1.4.2",
4
4
  "description": "Complete Instagram MQTT protocol with full-featured REALTIME and REST API — all in one project.",
5
5
 
6
6
  "main": "dist/dist/index.js",