nodejs-insta-private-api-mqtt 1.3.24 → 1.3.25

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.
@@ -3,11 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.mqttotConnectFlow = exports.MQTToTClient = void 0;
4
4
  const shared_1 = require("../shared");
5
5
  const mqttot_connect_request_packet_1 = require("./mqttot.connect.request.packet");
6
- // *** Schimbare: importăm mqtts din pachet extern în loc de mqtt-shim relativ ***
6
+ // *** Change: import the external mqtts package instead of a local mqtt-shim ***
7
7
  const mqtts_1 = require("mqtts");
8
8
  const errors_1 = require("../errors");
9
9
  const mqttot_connect_response_packet_1 = require("./mqttot.connect.response.packet");
10
10
 
11
+ /**
12
+ * MQTToTClient
13
+ * - Subclasses the external mqtts.MqttClient implementation.
14
+ * - Provides Instagram-specific connect/publish helpers (Thrift payload, debug).
15
+ * - Keeps compatibility with options used by the original library.
16
+ */
11
17
  class MQTToTClient extends mqtts_1.MqttClient {
12
18
  constructor(options) {
13
19
  super({
@@ -33,6 +39,7 @@ class MQTToTClient extends mqtts_1.MqttClient {
33
39
  additionalOptions: options.additionalOptions,
34
40
  }),
35
41
  });
42
+ // small debug helper that prefixes messages with the broker url
36
43
  this.mqttotDebug = (msg, ...args) => (0, shared_1.debugChannel)('mqttot')(`${options.url}: ${msg}`, ...args);
37
44
  this.connectPayloadProvider = options.payloadProvider;
38
45
  this.mqttotDebug(`Creating client`);
@@ -48,11 +55,13 @@ class MQTToTClient extends mqtts_1.MqttClient {
48
55
  this.mqttotDebug(`${type}: ${e.message}\n\tStack: ${e.stack}`);
49
56
  }
50
57
  };
58
+ // Attach basic diagnostics so errors/warnings are visible via debugChannel
51
59
  this.on('error', printErrorOrWarning('Error'));
52
60
  this.on('warning', printErrorOrWarning('Warning'));
53
61
  this.on('disconnect', e => this.mqttotDebug(`Disconnected. ${e}`));
54
62
  }
55
63
  async connect(options) {
64
+ // Acquire the payload (Thrift serialized connection blob) before connecting.
56
65
  this.connectPayload = await this.connectPayloadProvider();
57
66
  return super.connect(options);
58
67
  }
@@ -63,20 +72,28 @@ class MQTToTClient extends mqtts_1.MqttClient {
63
72
  return mqttotConnectFlow(this.connectPayload, this.requirePayload);
64
73
  }
65
74
  /**
66
- * Compresses the payload
75
+ * Compresses the payload and publishes it.
76
+ * NOTE: Always publishes with qosLevel: 0 to avoid puback/retry instability.
77
+ * Rationale: Instagram/edge MQTT sometimes behaves inconsistently with QoS1 (missing PUBACKs,
78
+ * delayed acks, etc.). For stability we force QoS 0 here so the library avoids waiting on ACKs
79
+ * that may never arrive and causing reconnect loops.
80
+ *
67
81
  * @param {MqttMessage} message
68
- * @returns {Promise<MqttMessageOutgoing>}
82
+ * @returns {Promise}
69
83
  */
70
84
  async mqttotPublish(message) {
71
85
  this.mqttotDebug(`Publishing ${message.payload.byteLength}bytes to topic ${message.topic}`);
72
86
  return await this.publish({
73
87
  topic: message.topic,
74
88
  payload: await (0, shared_1.compressDeflate)(message.payload),
75
- qosLevel: message.qosLevel,
89
+ qosLevel: 0, // FORCED: QoS 0 for stability on Instagram edge
76
90
  });
77
91
  }
78
92
  /**
79
93
  * Special listener for specific topics with transformers
94
+ * This helper attaches a transformer that converts raw message payloads into
95
+ * structured data before calling the provided handler.
96
+ *
80
97
  * @param {Object} config - { topic, transformer }
81
98
  * @param {Function} handler - Callback to handle transformed data
82
99
  */
@@ -99,7 +116,7 @@ exports.MQTToTClient = MQTToTClient;
99
116
 
100
117
  /**
101
118
  * NOTE: changed acceptance logic for CONNACK payload:
102
- * - Historically some connect flows expected a payload in CONNACK (requirePayload = true),
119
+ * - Historically the connect flow expected a payload in CONNACK (requirePayload = true),
103
120
  * but Instagram/edge sometimes replies with an empty payload even when connection is valid.
104
121
  * - To avoid noisy 'CONNACK: no payload (payloadExpected)' errors and unnecessary disconnect logs,
105
122
  * we now treat any successful CONNACK (packet.isSuccess) as success regardless of payload.
@@ -128,3 +145,4 @@ function mqttotConnectFlow(payload, requirePayload) {
128
145
  });
129
146
  }
130
147
  exports.mqttotConnectFlow = mqttotConnectFlow;
148
+
@@ -10,22 +10,23 @@ class Commands {
10
10
 
11
11
  /**
12
12
  * Publish a compressed payload to a topic.
13
- * - Will force QoS 0 (no PUBACK wait) because IG internal MQTT often
14
- * doesn't reply PUBACK for these internal topics and QoS1 causes timeouts.
15
- * - Will no-op if client is not connected to avoid "Socket not writable" errors.
13
+ * - Forces QoS 0 (no PUBACK wait) because Instagram internal MQTT often
14
+ * doesn't reply PUBACK for these internal topics and QoS1 causes timeouts
15
+ * and reconnect instability.
16
+ * - No-op if client is not connected to avoid "Socket not writable" errors.
17
+ * - Defensive checks: if the client exposes .connected or .isConnected(), respect them.
16
18
  */
17
19
  async publishToTopic(topic, compressedData, qos = 0) {
18
20
  try {
19
- // Guard: ensure client exists and is connected. Many mqtt clients expose
20
- // .connected boolean; this is defensive — if not present, still try but safe-guard write errors.
21
+ // Defensive: ensure we have a client
21
22
  if (!this.client) return;
22
23
 
23
- // prefer explicit connected flag if available
24
+ // If client exposes a boolean 'connected' flag, respect it
24
25
  if (typeof this.client.connected !== 'undefined' && !this.client.connected) {
25
- // do not attempt publish when disconnected
26
26
  return;
27
27
  }
28
- // If client exposes isConnected method, respect it
28
+
29
+ // If client exposes an isConnected() method, respect it
29
30
  if (typeof this.client.isConnected === 'function' && !this.client.isConnected()) {
30
31
  return;
31
32
  }
@@ -33,38 +34,38 @@ class Commands {
33
34
  // Build payload buffer
34
35
  const payloadBuf = compressedData instanceof Buffer ? compressedData : Buffer.from(compressedData);
35
36
 
36
- // Always use QoS 0 for IG internal topics (avoid PubAck waiting)
37
+ // Always publish with QoS 0 for stability on IG edge MQTT
37
38
  return this.client.publish({
38
39
  topic,
39
40
  payload: payloadBuf,
40
41
  qosLevel: 0,
41
42
  });
42
43
  } catch (err) {
43
- // Swallow non-fatal publish errors and let higher-level watchdog handle reconnects.
44
- // Avoid throwing so callers (watchdog timers) won't crash the loop.
44
+ // Swallow non-fatal publish errors. Upper layers/watchdog should handle reconnections.
45
45
  return;
46
46
  }
47
47
  }
48
48
 
49
49
  /**
50
- * updateSubscriptions used by RealtimeClient for various keepalive / subscribe refreshes.
51
- * We compress the body and publish with QoS 0.
52
- * If MQTT client is not ready, we skip the call.
50
+ * updateSubscriptions used by RealtimeClient for keepalive / subscription refreshes.
51
+ * - Compresses the subscription payload and publishes with QoS 0.
52
+ * - Skips publishing if the MQTT client doesn't appear ready.
53
53
  */
54
54
  async updateSubscriptions(options) {
55
55
  try {
56
56
  if (!this.client) return;
57
57
 
58
- // guard connection state again (defensive)
58
+ // Defensive connection checks
59
59
  if (typeof this.client.connected !== 'undefined' && !this.client.connected) return;
60
60
  if (typeof this.client.isConnected === 'function' && !this.client.isConnected()) return;
61
61
 
62
62
  const payload = await (0, shared_1.compressDeflate)(JSON.stringify(options.data || {}));
63
63
  return this.publishToTopic(options.topic.id, payload, 0);
64
64
  } catch (e) {
65
- // Non-fatal
65
+ // Non-fatal — avoid bubbling errors from keepalive timers
66
66
  return;
67
67
  }
68
68
  }
69
69
  }
70
70
  exports.Commands = Commands;
71
+
@@ -58,7 +58,7 @@ class EnhancedDirectCommands {
58
58
  const json = JSON.stringify(command);
59
59
  const payload = await shared_1.compressDeflate(json);
60
60
 
61
- // Send to MQTT (QoS 1 for message reliability)
61
+ // Send to MQTT (QoS 0 for stability)
62
62
  this.enhancedDebug(`Publishing to MQTT topic ${constants_1.Topics.SEND_MESSAGE.id}`);
63
63
  const result = await mqtt.publish({
64
64
  topic: constants_1.Topics.SEND_MESSAGE.id,
@@ -101,7 +101,7 @@ class EnhancedDirectCommands {
101
101
  this.enhancedDebug(`Publishing delete command to MQTT topic ${constants_1.Topics.SEND_MESSAGE.id}`);
102
102
  const result = await mqtt.publish({
103
103
  topic: constants_1.Topics.SEND_MESSAGE.id,
104
- qosLevel: 1,
104
+ qosLevel: 0,
105
105
  payload: payload,
106
106
  });
107
107
 
@@ -141,7 +141,7 @@ class EnhancedDirectCommands {
141
141
  this.enhancedDebug(`Publishing edit command to MQTT topic ${constants_1.Topics.SEND_MESSAGE.id}`);
142
142
  const result = await mqtt.publish({
143
143
  topic: constants_1.Topics.SEND_MESSAGE.id,
144
- qosLevel: 1,
144
+ qosLevel: 0,
145
145
  payload: payload,
146
146
  });
147
147
 
@@ -182,7 +182,7 @@ class EnhancedDirectCommands {
182
182
  this.enhancedDebug(`Publishing reply command to MQTT topic ${constants_1.Topics.SEND_MESSAGE.id}`);
183
183
  const result = await mqtt.publish({
184
184
  topic: constants_1.Topics.SEND_MESSAGE.id,
185
- qosLevel: 1,
185
+ qosLevel: 0,
186
186
  payload: payload,
187
187
  });
188
188
 
@@ -220,7 +220,7 @@ class EnhancedDirectCommands {
220
220
  this.enhancedDebug(`Publishing follow subscription to MQTT`);
221
221
  const result = await mqtt.publish({
222
222
  topic: constants_1.Topics.SEND_MESSAGE.id,
223
- qosLevel: 1,
223
+ qosLevel: 0,
224
224
  payload: payload,
225
225
  });
226
226
 
@@ -258,7 +258,7 @@ class EnhancedDirectCommands {
258
258
  this.enhancedDebug(`Publishing mention subscription to MQTT`);
259
259
  const result = await mqtt.publish({
260
260
  topic: constants_1.Topics.SEND_MESSAGE.id,
261
- qosLevel: 1,
261
+ qosLevel: 0,
262
262
  payload: payload,
263
263
  });
264
264
 
@@ -296,7 +296,7 @@ class EnhancedDirectCommands {
296
296
  this.enhancedDebug(`Publishing call subscription to MQTT`);
297
297
  const result = await mqtt.publish({
298
298
  topic: constants_1.Topics.SEND_MESSAGE.id,
299
- qosLevel: 1,
299
+ qosLevel: 0,
300
300
  payload: payload,
301
301
  });
302
302
 
@@ -335,7 +335,7 @@ class EnhancedDirectCommands {
335
335
  this.enhancedDebug(`Publishing add member command to MQTT`);
336
336
  const result = await mqtt.publish({
337
337
  topic: constants_1.Topics.SEND_MESSAGE.id,
338
- qosLevel: 1,
338
+ qosLevel: 0,
339
339
  payload: payload,
340
340
  });
341
341
 
@@ -374,7 +374,7 @@ class EnhancedDirectCommands {
374
374
  this.enhancedDebug(`Publishing remove member command to MQTT`);
375
375
  const result = await mqtt.publish({
376
376
  topic: constants_1.Topics.SEND_MESSAGE.id,
377
- qosLevel: 1,
377
+ qosLevel: 0,
378
378
  payload: payload,
379
379
  });
380
380
 
@@ -416,7 +416,7 @@ class EnhancedDirectCommands {
416
416
  this.enhancedDebug(`Publishing reaction to MQTT`);
417
417
  const result = await mqtt.publish({
418
418
  topic: constants_1.Topics.SEND_MESSAGE.id,
419
- qosLevel: 1,
419
+ qosLevel: 0,
420
420
  payload: payload,
421
421
  });
422
422
 
@@ -455,7 +455,7 @@ class EnhancedDirectCommands {
455
455
  this.enhancedDebug(`Publishing mark as seen to MQTT`);
456
456
  const result = await mqtt.publish({
457
457
  topic: constants_1.Topics.SEND_MESSAGE.id,
458
- qosLevel: 1,
458
+ qosLevel: 0,
459
459
  payload: payload,
460
460
  });
461
461
 
@@ -494,7 +494,7 @@ class EnhancedDirectCommands {
494
494
  this.enhancedDebug(`Publishing activity indicator to MQTT`);
495
495
  const result = await mqtt.publish({
496
496
  topic: constants_1.Topics.SEND_MESSAGE.id,
497
- qosLevel: 1,
497
+ qosLevel: 0,
498
498
  payload: payload,
499
499
  });
500
500
 
@@ -535,7 +535,7 @@ class EnhancedDirectCommands {
535
535
  this.enhancedDebug(`Publishing media to MQTT`);
536
536
  const result = await mqtt.publish({
537
537
  topic: constants_1.Topics.SEND_MESSAGE.id,
538
- qosLevel: 1,
538
+ qosLevel: 0,
539
539
  payload: payload,
540
540
  });
541
541
 
@@ -576,7 +576,7 @@ class EnhancedDirectCommands {
576
576
  this.enhancedDebug(`Publishing location to MQTT`);
577
577
  const result = await mqtt.publish({
578
578
  topic: constants_1.Topics.SEND_MESSAGE.id,
579
- qosLevel: 1,
579
+ qosLevel: 0,
580
580
  payload: payload,
581
581
  });
582
582
 
@@ -617,7 +617,7 @@ class EnhancedDirectCommands {
617
617
  this.enhancedDebug(`Publishing profile to MQTT`);
618
618
  const result = await mqtt.publish({
619
619
  topic: constants_1.Topics.SEND_MESSAGE.id,
620
- qosLevel: 1,
620
+ qosLevel: 0,
621
621
  payload: payload,
622
622
  });
623
623
 
@@ -658,7 +658,7 @@ class EnhancedDirectCommands {
658
658
  this.enhancedDebug(`Publishing hashtag to MQTT`);
659
659
  const result = await mqtt.publish({
660
660
  topic: constants_1.Topics.SEND_MESSAGE.id,
661
- qosLevel: 1,
661
+ qosLevel: 0,
662
662
  payload: payload,
663
663
  });
664
664
 
@@ -697,7 +697,7 @@ class EnhancedDirectCommands {
697
697
  this.enhancedDebug(`Publishing like to MQTT`);
698
698
  const result = await mqtt.publish({
699
699
  topic: constants_1.Topics.SEND_MESSAGE.id,
700
- qosLevel: 1,
700
+ qosLevel: 0,
701
701
  payload: payload,
702
702
  });
703
703
 
@@ -738,7 +738,7 @@ class EnhancedDirectCommands {
738
738
  this.enhancedDebug(`Publishing story to MQTT`);
739
739
  const result = await mqtt.publish({
740
740
  topic: constants_1.Topics.SEND_MESSAGE.id,
741
- qosLevel: 1,
741
+ qosLevel: 0,
742
742
  payload: payload,
743
743
  });
744
744
 
@@ -1,12 +1,24 @@
1
1
  // realtime.client.js
2
2
  'use strict';
3
3
 
4
- // (păstrează headerele module imports la fel ca în codul tău original)
4
+ /*
5
+ RealtimeClient
6
+ --------------
7
+ This file implements the RealtimeClient which manages:
8
+ - constructing the FBNS/MQTT connection payload
9
+ - starting the MQTT client (MQTToTClient)
10
+ - attaching lifecycle handlers (connect/close/error)
11
+ - keepalives, message-sync refresh and traffic watchdogs
12
+ - wiring MQTT messages into higher-level events (message, iris, receive)
13
+ Comments below are English explanations added next to the existing logic.
14
+ */
15
+
16
+ // (keep module imports headers the same as in your original code)
5
17
  const constants_1 = require("../constants");
6
18
  const commands_1 = require("./commands");
7
19
  const shared_1 = require("../shared");
8
20
  const mqttot_1 = require("../mqttot");
9
- // IMPORT MQTTS în loc de shim local
21
+ // IMPORT MQTTS instead of the local shim (we're using mqtts package)
10
22
  const mqtts_1 = require("mqtts");
11
23
  const errors_1 = require("../errors");
12
24
  const eventemitter3_1 = require("eventemitter3");
@@ -20,20 +32,40 @@ const gap_handler_1 = require("./features/gap-handler");
20
32
  const enhanced_direct_commands_1 = require("./commands/enhanced.direct.commands");
21
33
  const presence_typing_mixin_1 = require("./mixins/presence-typing.mixin");
22
34
 
35
+ /**
36
+ * RealtimeClient
37
+ * - Extends EventEmitter to emit high-level events for consumers.
38
+ * - Responsible for constructing the thrift payload, creating the MQTToTClient and wiring its events.
39
+ */
23
40
  class RealtimeClient extends eventemitter3_1.EventEmitter {
41
+ // getter to expose the underlying mqtt client instance
24
42
  get mqtt() {
25
43
  return this._mqtt;
26
44
  }
45
+
46
+ /**
47
+ * constructor(ig, mixins)
48
+ * - ig: instance of instagram client (used for building payloads & fetching inbox)
49
+ * - mixins: collection of mixins applied to this realtime client (message-sync, realtime-sub, presence/typing)
50
+ */
27
51
  constructor(ig, mixins = [new mixins_1.MessageSyncMixin(), new mixins_1.RealtimeSubMixin(), new presence_typing_mixin_1.PresenceTypingMixin()]) {
28
52
  super();
53
+ // debug helpers
29
54
  this.realtimeDebug = (0, shared_1.debugChannel)('realtime');
30
55
  this.messageDebug = this.realtimeDebug.extend('message');
56
+
57
+ // safeDisconnect flag used when user intentionally disconnects
31
58
  this.safeDisconnect = false;
59
+
60
+ // convenience wrappers to emit events
32
61
  this.emitError = (e) => this.emit('error', e);
33
62
  this.emitWarning = (e) => this.emit('warning', e);
63
+
64
+ // persistent references
34
65
  this.ig = ig;
35
66
  this.threads = new Map();
36
-
67
+
68
+ // instantiate features / protocols / mixins
37
69
  this.irisHandshake = new iris_handshake_1.IrisHandshake(this);
38
70
  this.skywalkerProtocol = new skywalker_protocol_1.SkywalkerProtocol(this);
39
71
  this.presenceManager = new presence_manager_1.PresenceManager(this);
@@ -41,8 +73,9 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
41
73
  this.errorHandler = new error_handler_1.ErrorHandler(this);
42
74
  this.gapHandler = new gap_handler_1.GapHandler(this);
43
75
  this.directCommands = new enhanced_direct_commands_1.EnhancedDirectCommands(this);
44
-
76
+
45
77
  this.realtimeDebug(`Applying mixins: ${mixins.map(m => m.name).join(', ')}`);
78
+ // apply mixins to this instance (keeps modular features separated)
46
79
  (0, mixins_1.applyMixins)(mixins, this, this.ig);
47
80
 
48
81
  // internal flags & timers
@@ -82,6 +115,11 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
82
115
  const fs = require('fs');
83
116
  const path = require('path');
84
117
 
118
+ /**
119
+ * waitForMqttCredentials(auth, timeoutMs, pollMs)
120
+ * - Poll the auth state for device / mqtt credentials before proceeding with auto-connect.
121
+ * - This ensures the saved auth state contains the fields required to build the MQTT payload.
122
+ */
85
123
  const waitForMqttCredentials = async (auth, timeoutMs = 15000, pollMs = 250) => {
86
124
  const start = Date.now();
87
125
  const hasCreds = () => {
@@ -103,6 +141,7 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
103
141
  return false;
104
142
  };
105
143
 
144
+ // Auto-start if saved creds exist on disk (non-blocking)
106
145
  if (fs.existsSync(path.join(folder, 'creds.json'))) {
107
146
  setTimeout(async () => {
108
147
  try {
@@ -134,6 +173,10 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
134
173
  }
135
174
  }
136
175
 
176
+ /**
177
+ * startRealTimeListener(options)
178
+ * - Convenience method to fetch initial inbox (IRIS) and connect with those subscriptions.
179
+ */
137
180
  async startRealTimeListener(options = {}) {
138
181
  try {
139
182
  console.log('[REALTIME] Starting Real-Time Listener...');
@@ -164,6 +207,7 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
164
207
  }
165
208
  }
166
209
 
210
+ // Attach convenience handlers that convert raw message events into higher-level events
167
211
  _setupMessageHandlers() {
168
212
  this.on('message', (data) => {
169
213
  const msg = this._parseMessage(data);
@@ -175,6 +219,7 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
175
219
  });
176
220
  }
177
221
 
222
+ // Parse a standard realtime message packet into a simplified message object
178
223
  _parseMessage(data) {
179
224
  try {
180
225
  const msg = data.message;
@@ -198,6 +243,7 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
198
243
  }
199
244
  }
200
245
 
246
+ // Parse IRIS message data into a simplified message object
201
247
  _parseIrisMessage(data) {
202
248
  try {
203
249
  if (data.event !== 'message_create' && !data.type?.includes('message')) return null;
@@ -216,6 +262,10 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
216
262
  }
217
263
  }
218
264
 
265
+ /**
266
+ * setInitOptions(initOptions)
267
+ * - Normalizes init options for connect calls (accepts array or object)
268
+ */
219
269
  setInitOptions(initOptions) {
220
270
  if (Array.isArray(initOptions))
221
271
  initOptions = { graphQlSubs: initOptions };
@@ -227,6 +277,7 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
227
277
  };
228
278
  }
229
279
 
280
+ // Extract session ID from a JWT-like authorization header if present
230
281
  extractSessionIdFromJWT() {
231
282
  try {
232
283
  const authHeader = this.ig.state.authorization;
@@ -254,6 +305,11 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
254
305
  }
255
306
  }
256
307
 
308
+ /**
309
+ * constructConnection()
310
+ * - Build the MQTToTConnection payload (thrift object) used to connect.
311
+ * - Pulls deviceId, sessionid, device secrets and app-specific headers to populate the payload.
312
+ */
257
313
  constructConnection() {
258
314
  const userAgent =
259
315
  typeof this.ig.state.userAgent === 'string'
@@ -319,6 +375,7 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
319
375
  }
320
376
  } catch (e) {}
321
377
 
378
+ // mqtt JWT token if available (preferred)
322
379
  let mqttJwt = null;
323
380
  try {
324
381
  if (this._attachedAuthState && typeof this._attachedAuthState.getData === 'function') {
@@ -350,6 +407,7 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
350
407
 
351
408
  const subscribeTopics = [88, 135, 149, 150, 133, 146];
352
409
 
410
+ // Build the thrift connection object using mqttot.MQTToTConnection
353
411
  this.connection = new mqttot_1.MQTToTConnection({
354
412
  clientIdentifier: deviceId.substring(0, 20),
355
413
  clientInfo: {
@@ -388,12 +446,20 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
388
446
  });
389
447
  }
390
448
 
449
+ /**
450
+ * connect(options)
451
+ * - Creates the MQTToTClient using mqttot.MQTToTClient and the payloadProvider.
452
+ * - Attaches idempotent lifecycle handlers for connect/close/error.
453
+ * - Starts keepalive/watchdog timers and message listeners after successful connect.
454
+ */
391
455
  async connect(options) {
392
456
  this.setInitOptions(options);
393
457
  this.constructConnection();
394
458
  const { MQTToTClient } = require("../mqttot");
395
459
  const { compressDeflate } = require("../shared");
396
-
460
+
461
+ // Create the MQTT client used by the runtime.
462
+ // requirePayload set to false to avoid treating empty CONNACK payload as fatal.
397
463
  this._mqtt = new MQTToTClient({
398
464
  url: 'edge-mqtt.facebook.com',
399
465
  payloadProvider: async () => {
@@ -402,8 +468,8 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
402
468
  autoReconnect: true,
403
469
  requirePayload: false,
404
470
  });
405
-
406
- // attach lifecycle handlers idempotent
471
+
472
+ // attach lifecycle handlers idempotent (create once)
407
473
  try {
408
474
  if (typeof this._attachMqttLifecycle !== 'function') {
409
475
  this._attachMqttLifecycle = async () => {
@@ -415,6 +481,7 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
415
481
  });
416
482
  } catch (e) {}
417
483
  try {
484
+ // handle close -> schedule reconnect attempt with debounce
418
485
  this._mqtt.on('close', async () => {
419
486
  this.realtimeDebug('[MQTT] client close event');
420
487
  this._lastMessageAt = Date.now();
@@ -425,6 +492,7 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
425
492
  });
426
493
  } catch (e) {}
427
494
  try {
495
+ // handle error -> schedule reconnect attempt with debounce
428
496
  this._mqtt.on('error', (err) => {
429
497
  this.realtimeDebug('[MQTT] client error:', err?.message || err);
430
498
  if (this._reconnectTimeoutId) clearTimeout(this._reconnectTimeoutId);
@@ -438,11 +506,13 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
438
506
  try { await this._attachMqttLifecycle(); } catch (e) {}
439
507
  } catch (e) {}
440
508
 
509
+ // actually connect the mqtt client (this will emit connect when done)
441
510
  await this._mqtt.connect();
442
-
443
- // Commands uses mqtt client; we changed Commands.updateSubscriptions to qos 0 (see file)
511
+
512
+ // Commands uses mqtt client; Commands.updateSubscriptions has been set to use qos 0.
444
513
  this.commands = new commands_1.Commands(this._mqtt);
445
-
514
+
515
+ // Notify higher-level code that we are connected
446
516
  this.emit('connected');
447
517
 
448
518
  // WATCHDOG / KEEPALIVE / TRAFFIC MONITOR
@@ -459,6 +529,7 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
459
529
  const TRAFFIC_INACTIVITY_MS = (this.initOptions && this.initOptions.trafficInactivityMs) ? this.initOptions.trafficInactivityMs : 90000;
460
530
 
461
531
  try {
532
+ // Foreground keepalive - periodically send a small "foreground true" subscription update
462
533
  if (this._foregroundTimer) clearInterval(this._foregroundTimer);
463
534
  this._foregroundTimer = setInterval(async () => {
464
535
  try {
@@ -477,6 +548,7 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
477
548
  }
478
549
 
479
550
  try {
551
+ // MESSAGE_SYNC refresh - re-subscribe GraphQL subscriptions periodically to keep message-sync fresh
480
552
  if (this._syncTimer) clearInterval(this._syncTimer);
481
553
  this._syncTimer = setInterval(async () => {
482
554
  try {
@@ -493,6 +565,7 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
493
565
  }
494
566
 
495
567
  try {
568
+ // Traffic watchdog - if no activity for TRAFFIC_INACTIVITY_MS, attempt reconnect
496
569
  if (this._trafficWatchdog) clearInterval(this._trafficWatchdog);
497
570
  this._trafficWatchdog = setInterval(async () => {
498
571
  try {
@@ -513,6 +586,11 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
513
586
  this.realtimeDebug('[WATCHDOG] initialization error:', e?.message || e);
514
587
  }
515
588
 
589
+ /**
590
+ * Set up on-message handler:
591
+ * - Messages arriving from low-level MQTT are looked up in mqtt.topicMap,
592
+ * then parsed by the topic's parser if available. Otherwise payload is emitted as raw.
593
+ */
516
594
  this._mqtt.on('message', async (msg) => {
517
595
  const topicMap = this.mqtt?.topicMap;
518
596
  const topic = topicMap?.get(msg.topic);
@@ -529,10 +607,12 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
529
607
  } catch(e) {}
530
608
  }
531
609
  });
610
+ // propagate mqtt errors to RealtimeClient error handler
532
611
  this._mqtt.on('error', this.emitError);
533
612
 
534
613
  try { await this._afterConnectHandlers(); } catch (e) { this.realtimeDebug('[MQTT] afterConnectHandlers failed', e?.message || e); }
535
614
 
615
+ // Initial subscriptions / iris
536
616
  await (0, shared_1.delay)(100);
537
617
  if (this.initOptions.graphQlSubs && this.initOptions.graphQlSubs.length > 0) {
538
618
  await this.graphQlSubscribe(this.initOptions.graphQlSubs);
@@ -567,6 +647,11 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
567
647
  this._setupMessageHandlers();
568
648
  }
569
649
 
650
+ /**
651
+ * _attemptReconnectSafely()
652
+ * - Ensure only a single reconnect flow runs at once.
653
+ * - Disconnects existing mqtt client, waits briefly, and calls connect() again.
654
+ */
570
655
  async _attemptReconnectSafely() {
571
656
  if (this._reconnectInProgress) return;
572
657
  this._reconnectInProgress = true;
@@ -586,6 +671,11 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
586
671
  }
587
672
  }
588
673
 
674
+ /**
675
+ * _ensureIrisSnapshotAndSubscribe()
676
+ * - Ensure IRIS snapshot is present and subscribe to it.
677
+ * - Tries a fresh fetch after connect to get the latest snapshot.
678
+ */
589
679
  async _ensureIrisSnapshotAndSubscribe() {
590
680
  try {
591
681
  let iris = this.initOptions?.irisData || null;
@@ -616,6 +706,10 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
616
706
  }
617
707
  }
618
708
 
709
+ /**
710
+ * Attach message_sync listener to mqtt if available
711
+ * - Some mqtt clients provide a listen() helper; attach to both numeric id and string names for compatibility.
712
+ */
619
713
  async _ensureMessageSyncListener() {
620
714
  try {
621
715
  if (this._messageSyncAttached) return;
@@ -647,6 +741,11 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
647
741
  }
648
742
  }
649
743
 
744
+ /**
745
+ * connectFromSavedSession(authStateHelper, options)
746
+ * - Reconstructs connect options from saved authState and then calls connect()
747
+ * - Attempts to fetch a fresh IRIS snapshot (up to a few attempts) for safety.
748
+ */
650
749
  async connectFromSavedSession(authStateHelper, options = {}) {
651
750
  if (!authStateHelper) {
652
751
  throw new Error('authStateHelper is required - use useMultiFileAuthState()');
@@ -723,6 +822,11 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
723
822
 
724
823
  return this;
725
824
  }
825
+
826
+ /**
827
+ * saveSession(authStateHelper)
828
+ * - Helper to persist current MQTT session to auth helper if available.
829
+ */
726
830
  async saveSession(authStateHelper) {
727
831
  if (!authStateHelper || !authStateHelper.saveMqttSession) {
728
832
  console.warn('[RealtimeClient] No authStateHelper provided');
@@ -731,6 +835,11 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
731
835
  await authStateHelper.saveMqttSession(this);
732
836
  return true;
733
837
  }
838
+
839
+ /**
840
+ * disconnect()
841
+ * - Perform a graceful shutdown: clear timers and disconnect mqtt client.
842
+ */
734
843
  disconnect() {
735
844
  this.safeDisconnect = true;
736
845
  try {
@@ -749,6 +858,11 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
749
858
  } catch (e) {}
750
859
  return this.mqtt?.disconnect() ?? Promise.resolve();
751
860
  }
861
+
862
+ /**
863
+ * graphQlSubscribe(sub)
864
+ * - Helper that delegates to Commands.updateSubscriptions (which uses qos 0).
865
+ */
752
866
  graphQlSubscribe(sub) {
753
867
  sub = typeof sub === 'string' ? [sub] : sub;
754
868
  if (!this.commands) {
@@ -762,6 +876,11 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
762
876
  },
763
877
  });
764
878
  }
879
+
880
+ /**
881
+ * skywalkerSubscribe(sub)
882
+ * - Delegate to Commands.updateSubscriptions (pubsub topic).
883
+ */
765
884
  skywalkerSubscribe(sub) {
766
885
  sub = typeof sub === 'string' ? [sub] : sub;
767
886
  if (!this.commands) {
@@ -775,6 +894,11 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
775
894
  },
776
895
  });
777
896
  }
897
+
898
+ /**
899
+ * irisSubscribe({ seq_id, snapshot_at_ms })
900
+ * - Subscribe to IRIS using the provided snapshot properties.
901
+ */
778
902
  irisSubscribe({ seq_id, snapshot_at_ms, }) {
779
903
  if (!this.commands) {
780
904
  throw new mqtts_1.IllegalStateError('connect() must be called before irisSubscribe()');
@@ -791,3 +915,4 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
791
915
  }
792
916
  }
793
917
  exports.RealtimeClient = RealtimeClient;
918
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodejs-insta-private-api-mqtt",
3
- "version": "1.3.24",
3
+ "version": "1.3.25",
4
4
  "description": "Complete Instagram MQTT protocol with FULL iOS + Android support. 33 device presets (21 iOS + 12 Android). iPhone 16/15/14/13/12, iPad Pro, Samsung, Pixel, Huawei. Real-time DM messaging, view-once media extraction, sub-500ms latency.",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {