nodejs-insta-private-api-mqtt 1.2.10 → 1.3.11

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.
@@ -17,6 +17,7 @@ const error_handler_1 = require("./features/error-handler");
17
17
  const gap_handler_1 = require("./features/gap-handler");
18
18
  const enhanced_direct_commands_1 = require("./commands/enhanced.direct.commands");
19
19
  const presence_typing_mixin_1 = require("./mixins/presence-typing.mixin");
20
+
20
21
  class RealtimeClient extends eventemitter3_1.EventEmitter {
21
22
  get mqtt() {
22
23
  return this._mqtt;
@@ -47,25 +48,75 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
47
48
  this.realtimeDebug(`Applying mixins: ${mixins.map(m => m.name).join(', ')}`);
48
49
  (0, mixins_1.applyMixins)(mixins, this, this.ig);
49
50
 
50
- // Auto-connect if session is available
51
+ // ---------- AUTO-CONNECT BLOCK (UPDATED to wait for MQTT creds like APK) ----------
52
+ // Keep folder same as before for compatibility, but WAIT until device/mqtt creds present
51
53
  const folder = './auth_info_ig';
52
54
  const { useMultiFileAuthState } = require('../useMultiFileAuthState');
53
55
  const fs = require('fs');
54
56
  const path = require('path');
57
+
58
+ // store optional attached authState for later use
59
+ this._attachedAuthState = null;
60
+
61
+ // helper: wait/poll until mqtt/device credentials available (CountDownLatch equivalent)
62
+ const waitForMqttCredentials = async (auth, timeoutMs = 15000, pollMs = 250) => {
63
+ const start = Date.now();
64
+ const hasCreds = () => {
65
+ try {
66
+ const d = (auth && typeof auth.getData === 'function') ? auth.getData() : (auth && auth.data ? auth.data : null);
67
+ if (!d) return false;
68
+ // Acceptable indicators: device.deviceSecret OR mqttAuth.jwt OR mqttAuth.deviceSecret
69
+ if (d.device && (d.device.deviceSecret || d.device.secret)) return true;
70
+ if (d.mqttAuth && (d.mqttAuth.jwt || d.mqttAuth.deviceSecret)) return true;
71
+ // fallback: creds/sessionid present (not ideal but better than nothing)
72
+ if (d.creds && (d.creds.sessionId || d.creds.csrfToken || d.creds.authorization)) return true;
73
+ return false;
74
+ } catch (e) {
75
+ return false;
76
+ }
77
+ };
78
+ // Poll until available or timeout
79
+ while (Date.now() - start < timeoutMs) {
80
+ if (hasCreds()) return true;
81
+ await new Promise(r => setTimeout(r, pollMs));
82
+ }
83
+ return false;
84
+ };
85
+
86
+ // Attempt auto-start only if creds.json exists — but wait for device/mqtt creds like APK does.
55
87
  if (fs.existsSync(path.join(folder, 'creds.json'))) {
56
88
  setTimeout(async () => {
57
89
  try {
58
90
  const auth = await useMultiFileAuthState(folder);
59
- if (auth.hasSession()) {
60
- console.log('[REALTIME] Auto-starting MQTT from constructor...');
61
- await auth.loadCreds(this.ig);
62
- await this.connectFromSavedSession(auth);
91
+ // attach for later use
92
+ this._attachedAuthState = auth;
93
+ if (auth.hasSession && auth.hasSession()) {
94
+ console.log('[REALTIME] Auto-start candidate session detected — loading creds...');
95
+ try {
96
+ await auth.loadCreds(this.ig);
97
+ } catch (e) {
98
+ // ignore load errors but log
99
+ console.warn('[REALTIME] loadCreds warning:', e?.message || e);
100
+ }
101
+ // Wait for MQTT/device credentials to be present (APK-like behaviour)
102
+ const ready = await waitForMqttCredentials(auth, 20000, 300);
103
+ if (!ready) {
104
+ console.warn('[REALTIME] MQTT/device credentials not found within timeout — auto-connect aborted (will still allow manual connect).');
105
+ return;
106
+ }
107
+ console.log('[REALTIME] Device/MQTT credentials present — attempting connectFromSavedSession...');
108
+ try {
109
+ await this.connectFromSavedSession(auth);
110
+ } catch (e) {
111
+ console.error('[REALTIME] Constructor auto-connect failed:', e?.message || e);
112
+ }
63
113
  }
64
114
  } catch (e) {
65
- console.error('[REALTIME] Constructor auto-connect failed:', e.message);
115
+ console.error('[REALTIME] Constructor auto-start exception:', e?.message || e);
66
116
  }
67
117
  }, 100);
68
118
  }
119
+ // ---------- END AUTO-CONNECT BLOCK ----------
69
120
  }
70
121
 
71
122
  /**
@@ -193,17 +244,32 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
193
244
  try {
194
245
  const authHeader = this.ig.state.authorization;
195
246
  if (!authHeader) return null;
196
- // Extract base64 part from "Bearer IGT:2:{base64}"
197
- const base64Part = authHeader.replace('Bearer IGT:2:', '').replace('Bearer ', '');
198
- // Decode from base64
199
- const decoded = Buffer.from(base64Part, 'base64').toString();
200
- const payload = JSON.parse(decoded);
201
- // Get sessionid and URL-decode it
202
- let sessionid = payload.sessionid;
203
- if (sessionid) {
204
- sessionid = decodeURIComponent(sessionid);
247
+ // Attempt to decode possible bearer formats safely
248
+ const raw = String(authHeader || '');
249
+ // strip known prefixes
250
+ const candidate = raw.replace(/^Bearer\s*/i, '').replace(/^IGT:2:/i, '');
251
+ // if contains '.', might be JWT-like (header.payload.sig)
252
+ if (candidate.includes('.')) {
253
+ const parts = candidate.split('.');
254
+ if (parts.length >= 2) {
255
+ try {
256
+ const payload = Buffer.from(parts[1], 'base64').toString('utf8');
257
+ const parsed = JSON.parse(payload);
258
+ return parsed.sessionid || parsed.session_id || parsed.session || null;
259
+ } catch (e) {
260
+ // ignore parse error
261
+ }
262
+ }
205
263
  }
206
- return sessionid || null;
264
+ // fallback: try base64 decode whole candidate
265
+ try {
266
+ const decoded = Buffer.from(candidate, 'base64').toString('utf8');
267
+ const parsed = JSON.parse(decoded);
268
+ return parsed.sessionid || parsed.session_id || null;
269
+ } catch (e) {
270
+ // ignore
271
+ }
272
+ return null;
207
273
  } catch (e) {
208
274
  return null;
209
275
  }
@@ -221,45 +287,106 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
221
287
 
222
288
  // Ensure deviceId is a string to avoid substring errors
223
289
  const deviceId = String(this.ig.state.phoneId || this.ig.state.deviceId || 'device_unknown');
224
- let sessionid;
225
- // First try: Extract from JWT authorization header (PRIMARY METHOD)
226
- sessionid = this.extractSessionIdFromJWT();
227
- if (sessionid) {
228
- this.realtimeDebug(`SessionID extracted from JWT: ${sessionid.substring(0, 20)}...`);
290
+
291
+ // Try multiple ways to determine sessionid/password and deviceSecret
292
+ let sessionid = null;
293
+ try {
294
+ // 1. Try extracting from JWT/Authorization header
295
+ sessionid = this.extractSessionIdFromJWT();
296
+ if (sessionid) {
297
+ this.realtimeDebug(`SessionID extracted from JWT-like auth: ${String(sessionid).substring(0, 20)}...`);
298
+ }
299
+ } catch (e) {
300
+ sessionid = null;
229
301
  }
230
- // Second try: Direct cookie lookup
302
+
303
+ // 2. Try state helpers (some libs expose extractCookieValue)
231
304
  if (!sessionid) {
232
305
  try {
233
- sessionid = this.ig.state.extractCookieValue('sessionid');
306
+ if (typeof this.ig.state.extractCookieValue === 'function') {
307
+ sessionid = this.ig.state.extractCookieValue('sessionid');
308
+ }
234
309
  } catch (e) {
235
310
  sessionid = null;
236
311
  }
237
312
  }
238
- // Third try: Parsed authorization
313
+
314
+ // 3. Try raw state fields
239
315
  if (!sessionid) {
240
316
  try {
241
- sessionid = this.ig.state.parsedAuthorization?.sessionid;
242
- } catch (e2) {
317
+ sessionid = this.ig.state.sessionId || this.ig.state.sessionid || this.ig.state.cookies?.sessionid || null;
318
+ } catch (e) {
243
319
  sessionid = null;
244
320
  }
245
321
  }
246
- // Fourth try: CookieJar introspection
322
+
323
+ // 4. Try cookieJar inspection (best effort)
247
324
  if (!sessionid) {
248
325
  try {
249
- const cookies = this.ig.state.cookieJar.getCookiesSync('https://i.instagram.com/');
250
- const sessionCookie = cookies.find(c => c.key === 'sessionid');
251
- sessionid = sessionCookie?.value;
326
+ if (this.ig.state.cookieJar && typeof this.ig.state.cookieJar.getCookiesSync === 'function') {
327
+ const cookies = this.ig.state.cookieJar.getCookiesSync('https://i.instagram.com/') || [];
328
+ const found = cookies.find(c => (c.key === 'sessionid' || c.name === 'sessionid'));
329
+ if (found) sessionid = found.value;
330
+ }
252
331
  } catch (e) {
253
- sessionid = null;
332
+ // ignore
254
333
  }
255
334
  }
256
- // Last resort: Generate from userId + timestamp
335
+
336
+ // 5. Last resort fallback (generated)
257
337
  if (!sessionid) {
258
338
  const userId = this.ig.state.cookieUserId || this.ig.state.userId || '0';
259
339
  sessionid = String(userId) + '_' + Date.now();
260
340
  this.realtimeDebug(`SessionID generated (fallback): ${sessionid}`);
261
341
  }
262
- const password = `sessionid=${sessionid}`;
342
+
343
+ // Determine deviceSecret if available via attached authState or ig.state
344
+ let deviceSecret = null;
345
+ try {
346
+ if (this._attachedAuthState && typeof this._attachedAuthState.getData === 'function') {
347
+ const d = this._attachedAuthState.getData();
348
+ if (d && d.device && (d.device.deviceSecret || d.device.secret)) {
349
+ deviceSecret = d.device.deviceSecret || d.device.secret;
350
+ }
351
+ // also check mqttAuth
352
+ if (!deviceSecret && d && d.mqttAuth && (d.mqttAuth.deviceSecret || d.mqttAuth.secret)) {
353
+ deviceSecret = d.mqttAuth.deviceSecret || d.mqttAuth.secret;
354
+ }
355
+ }
356
+ } catch (e) {}
357
+ // also try ig.state
358
+ try {
359
+ if (!deviceSecret && (this.ig.state.deviceSecret || this.ig.state.mqttDeviceSecret)) {
360
+ deviceSecret = this.ig.state.deviceSecret || this.ig.state.mqttDeviceSecret;
361
+ }
362
+ } catch (e) {}
363
+
364
+ // Determine mqttAuth token if present
365
+ let mqttJwt = null;
366
+ try {
367
+ if (this._attachedAuthState && typeof this._attachedAuthState.getData === 'function') {
368
+ const d = this._attachedAuthState.getData();
369
+ if (d && d.mqttAuth && d.mqttAuth.jwt) mqttJwt = d.mqttAuth.jwt;
370
+ }
371
+ // fallback to ig.state
372
+ if (!mqttJwt && this.ig.state.mqttJwt) mqttJwt = this.ig.state.mqttJwt;
373
+ } catch (e) {}
374
+
375
+ // Build password: prefer mqttJwt if present, else sessionid style string
376
+ let password;
377
+ if (mqttJwt) {
378
+ password = `jwt=${mqttJwt}`;
379
+ } else {
380
+ password = `sessionid=${sessionid}`;
381
+ }
382
+
383
+ // Determine clientType and other clientInfo modifications if secure deviceSecret exists
384
+ const clientType = deviceSecret ? 'secure_cookie_auth' : 'cookie_auth';
385
+
386
+ const clientMqttSessionId = (BigInt(Date.now()) & BigInt(0xffffffff));
387
+
388
+ const subscribeTopics = [88, 135, 149, 150, 133, 146];
389
+
263
390
  this.connection = new mqttot_1.MQTToTConnection({
264
391
  clientIdentifier: deviceId.substring(0, 20),
265
392
  clientInfo: {
@@ -274,11 +401,11 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
274
401
  isInitiallyForeground: true,
275
402
  networkType: 1,
276
403
  networkSubtype: 0,
277
- clientMqttSessionId: BigInt(Date.now()) & BigInt(0xffffffff),
278
- subscribeTopics: [88, 135, 149, 150, 133, 146],
279
- clientType: 'cookie_auth',
404
+ clientMqttSessionId: clientMqttSessionId,
405
+ subscribeTopics,
406
+ clientType,
280
407
  appId: BigInt(567067343352427),
281
- deviceSecret: '',
408
+ deviceSecret: deviceSecret || '',
282
409
  clientStack: 3,
283
410
  ...(this.initOptions?.connectOverrides || {}),
284
411
  },
@@ -390,6 +517,11 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
390
517
 
391
518
  console.log('[RealtimeClient] Connecting from saved session...');
392
519
 
520
+ // Attach authState to this instance so constructConnection can read deviceSecret/mqttAuth
521
+ try {
522
+ this._attachedAuthState = authStateHelper;
523
+ } catch (e) {}
524
+
393
525
  const savedOptions = authStateHelper.getMqttConnectOptions?.();
394
526
 
395
527
  const connectOptions = {
@@ -405,6 +537,18 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
405
537
  hasIrisData: !!connectOptions.irisData
406
538
  });
407
539
 
540
+ // If authState has mqttAuth with expiresAt, optionally warn if expired (non-fatal)
541
+ try {
542
+ const d = authStateHelper.getData?.() || authStateHelper.data || {};
543
+ const mqttAuth = d.mqttAuth || null;
544
+ if (mqttAuth && mqttAuth.expiresAt) {
545
+ const t = new Date(mqttAuth.expiresAt).getTime();
546
+ if (!isNaN(t) && Date.now() > t) {
547
+ console.warn('[RealtimeClient] Warning: saved mqttAuth token appears expired.');
548
+ }
549
+ }
550
+ } catch (e) {}
551
+
408
552
  await this.connect(connectOptions);
409
553
 
410
554
  if (authStateHelper.saveMqttSession) {