nodejs-insta-private-api-mqtt 1.1.10 → 1.2.10
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/dist/useMultiFileAuthState.js +769 -43
- package/package.json +1 -1
|
@@ -5,15 +5,28 @@ const path = require('path');
|
|
|
5
5
|
const { CookieJar } = require('tough-cookie');
|
|
6
6
|
const util = require('util');
|
|
7
7
|
const EventEmitter = require('events');
|
|
8
|
+
const crypto = require('crypto');
|
|
8
9
|
|
|
9
10
|
const FILE_NAMES = {
|
|
10
11
|
creds: 'creds.json',
|
|
11
|
-
device: 'device.json',
|
|
12
|
+
device: 'device.json',
|
|
12
13
|
cookies: 'cookies.json',
|
|
13
14
|
mqttSession: 'mqtt-session.json',
|
|
14
15
|
subscriptions: 'subscriptions.json',
|
|
15
16
|
seqIds: 'seq-ids.json',
|
|
16
|
-
appState: 'app-state.json'
|
|
17
|
+
appState: 'app-state.json',
|
|
18
|
+
|
|
19
|
+
// Newly added MQTT / realtime persistence files
|
|
20
|
+
irisState: 'iris-state.json', // subscription / iris specific state
|
|
21
|
+
mqttTopics: 'mqtt-topics.json', // observed topics
|
|
22
|
+
mqttCapabilities: 'mqtt-capabilities.json', // realtime capabilities per device/version
|
|
23
|
+
mqttAuth: 'mqtt-auth.json', // mqtt auth token / jwt (if available)
|
|
24
|
+
lastPublishIds: 'last-publish-ids.json', // last published message ids / ack info
|
|
25
|
+
|
|
26
|
+
// Added utility files
|
|
27
|
+
loginHistory: 'login-history.json',
|
|
28
|
+
usageStats: 'usage-stats.json',
|
|
29
|
+
accountsIndex: 'accounts.json'
|
|
17
30
|
};
|
|
18
31
|
|
|
19
32
|
class MultiFileAuthState extends EventEmitter {
|
|
@@ -23,7 +36,12 @@ class MultiFileAuthState extends EventEmitter {
|
|
|
23
36
|
this._saveDebounceTimer = null;
|
|
24
37
|
this._saveDebounceMs = 500;
|
|
25
38
|
this._dirty = new Set();
|
|
26
|
-
|
|
39
|
+
|
|
40
|
+
// Auto-refresh fields
|
|
41
|
+
this._autoRefreshTimer = null;
|
|
42
|
+
this._autoRefreshIntervalMs = 5 * 60 * 1000; // default 5 minutes
|
|
43
|
+
this._autoRefreshClient = null;
|
|
44
|
+
|
|
27
45
|
this.data = {
|
|
28
46
|
creds: null,
|
|
29
47
|
device: null,
|
|
@@ -31,20 +49,66 @@ class MultiFileAuthState extends EventEmitter {
|
|
|
31
49
|
mqttSession: null,
|
|
32
50
|
subscriptions: null,
|
|
33
51
|
seqIds: null,
|
|
34
|
-
appState: null
|
|
52
|
+
appState: null,
|
|
53
|
+
|
|
54
|
+
// new
|
|
55
|
+
irisState: null,
|
|
56
|
+
mqttTopics: null,
|
|
57
|
+
mqttCapabilities: null,
|
|
58
|
+
mqttAuth: null,
|
|
59
|
+
lastPublishIds: null,
|
|
60
|
+
|
|
61
|
+
// utility
|
|
62
|
+
loginHistory: null,
|
|
63
|
+
usageStats: null,
|
|
64
|
+
accountsIndex: null
|
|
35
65
|
};
|
|
66
|
+
|
|
67
|
+
// ensure folder structure
|
|
68
|
+
this._ensureFolder();
|
|
69
|
+
this._ensureBackupFolder();
|
|
36
70
|
}
|
|
37
71
|
|
|
38
72
|
_getFilePath(key) {
|
|
39
73
|
return path.join(this.folder, FILE_NAMES[key]);
|
|
40
74
|
}
|
|
41
75
|
|
|
76
|
+
_getBackupFolder() {
|
|
77
|
+
return path.join(this.folder, 'backup');
|
|
78
|
+
}
|
|
79
|
+
|
|
42
80
|
_ensureFolder() {
|
|
43
81
|
if (!fs.existsSync(this.folder)) {
|
|
44
82
|
fs.mkdirSync(this.folder, { recursive: true, mode: 0o700 });
|
|
45
83
|
}
|
|
46
84
|
}
|
|
47
85
|
|
|
86
|
+
_ensureBackupFolder() {
|
|
87
|
+
const b = this._getBackupFolder();
|
|
88
|
+
if (!fs.existsSync(b)) {
|
|
89
|
+
fs.mkdirSync(b, { recursive: true, mode: 0o700 });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// create a timestamped backup of existing file (if present)
|
|
94
|
+
async _createBackup(key) {
|
|
95
|
+
try {
|
|
96
|
+
const filePath = this._getFilePath(key);
|
|
97
|
+
if (fs.existsSync(filePath)) {
|
|
98
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
99
|
+
const base = path.basename(filePath);
|
|
100
|
+
const dest = path.join(this._getBackupFolder(), `${base}.${ts}.bak`);
|
|
101
|
+
await fs.promises.copyFile(filePath, dest);
|
|
102
|
+
this.emit('backup-created', { key, file: dest });
|
|
103
|
+
return dest;
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {
|
|
106
|
+
// ignore errors in backup process but log
|
|
107
|
+
console.warn('[MultiFileAuthState] Backup creation failed for', key, e.message);
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
48
112
|
async _writeFileAtomic(filePath, data) {
|
|
49
113
|
const tempPath = filePath + '.tmp';
|
|
50
114
|
const jsonData = JSON.stringify(data, null, 2);
|
|
@@ -69,6 +133,8 @@ class MultiFileAuthState extends EventEmitter {
|
|
|
69
133
|
this._ensureFolder();
|
|
70
134
|
const filePath = this._getFilePath(key);
|
|
71
135
|
try {
|
|
136
|
+
// create backup before overwriting existing file
|
|
137
|
+
await this._createBackup(key);
|
|
72
138
|
await this._writeFileAtomic(filePath, data);
|
|
73
139
|
return true;
|
|
74
140
|
} catch (e) {
|
|
@@ -77,15 +143,52 @@ class MultiFileAuthState extends EventEmitter {
|
|
|
77
143
|
}
|
|
78
144
|
}
|
|
79
145
|
|
|
146
|
+
// restore the latest backup for a key (returns path of restored file or null)
|
|
147
|
+
async restoreLatestBackup(key) {
|
|
148
|
+
try {
|
|
149
|
+
const base = FILE_NAMES[key];
|
|
150
|
+
const bfolder = this._getBackupFolder();
|
|
151
|
+
if (!fs.existsSync(bfolder)) return null;
|
|
152
|
+
const files = await fs.promises.readdir(bfolder);
|
|
153
|
+
const matches = files.filter(f => f.startsWith(base + '.'));
|
|
154
|
+
if (!matches.length) return null;
|
|
155
|
+
// sort descending by timestamp embedded in filename
|
|
156
|
+
matches.sort().reverse();
|
|
157
|
+
const latest = matches[0];
|
|
158
|
+
const src = path.join(bfolder, latest);
|
|
159
|
+
const dest = this._getFilePath(key);
|
|
160
|
+
await fs.promises.copyFile(src, dest);
|
|
161
|
+
this.emit('backup-restored', { key, file: src });
|
|
162
|
+
// reload into memory
|
|
163
|
+
this.data[key] = await this._readFile(key);
|
|
164
|
+
return src;
|
|
165
|
+
} catch (e) {
|
|
166
|
+
console.warn('[MultiFileAuthState] restoreLatestBackup error:', e.message);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
80
171
|
async loadAll() {
|
|
81
172
|
this._ensureFolder();
|
|
82
|
-
|
|
173
|
+
this._ensureBackupFolder();
|
|
174
|
+
|
|
83
175
|
const loadPromises = Object.keys(FILE_NAMES).map(async (key) => {
|
|
84
176
|
this.data[key] = await this._readFile(key);
|
|
85
177
|
});
|
|
86
|
-
|
|
178
|
+
|
|
87
179
|
await Promise.all(loadPromises);
|
|
88
|
-
|
|
180
|
+
|
|
181
|
+
// ensure usageStats and loginHistory objects exist
|
|
182
|
+
if (!this.data.loginHistory) this.data.loginHistory = [];
|
|
183
|
+
if (!this.data.usageStats) this.data.usageStats = {
|
|
184
|
+
apiRequests: 0,
|
|
185
|
+
mqttMessages: 0,
|
|
186
|
+
errors: 0,
|
|
187
|
+
reconnects: 0,
|
|
188
|
+
lastReset: new Date().toISOString()
|
|
189
|
+
};
|
|
190
|
+
if (!this.data.accountsIndex) this.data.accountsIndex = {};
|
|
191
|
+
|
|
89
192
|
return {
|
|
90
193
|
creds: this.data.creds,
|
|
91
194
|
device: this.data.device,
|
|
@@ -94,6 +197,14 @@ class MultiFileAuthState extends EventEmitter {
|
|
|
94
197
|
subscriptions: this.data.subscriptions,
|
|
95
198
|
seqIds: this.data.seqIds,
|
|
96
199
|
appState: this.data.appState,
|
|
200
|
+
irisState: this.data.irisState,
|
|
201
|
+
mqttTopics: this.data.mqttTopics,
|
|
202
|
+
mqttCapabilities: this.data.mqttCapabilities,
|
|
203
|
+
mqttAuth: this.data.mqttAuth,
|
|
204
|
+
lastPublishIds: this.data.lastPublishIds,
|
|
205
|
+
loginHistory: this.data.loginHistory,
|
|
206
|
+
usageStats: this.data.usageStats,
|
|
207
|
+
accountsIndex: this.data.accountsIndex,
|
|
97
208
|
hasSession: !!(this.data.creds && this.data.cookies)
|
|
98
209
|
};
|
|
99
210
|
}
|
|
@@ -104,6 +215,16 @@ class MultiFileAuthState extends EventEmitter {
|
|
|
104
215
|
await this._writeFile(key, this.data[key]);
|
|
105
216
|
}
|
|
106
217
|
});
|
|
218
|
+
// also save usageStats and loginHistory explicitly
|
|
219
|
+
if (this.data.loginHistory) {
|
|
220
|
+
try { await this._writeFile('loginHistory', this.data.loginHistory); } catch (e) {}
|
|
221
|
+
}
|
|
222
|
+
if (this.data.usageStats) {
|
|
223
|
+
try { await this._writeFile('usageStats', this.data.usageStats); } catch (e) {}
|
|
224
|
+
}
|
|
225
|
+
if (this.data.accountsIndex) {
|
|
226
|
+
try { await this._writeFile('accountsIndex', this.data.accountsIndex); } catch (e) {}
|
|
227
|
+
}
|
|
107
228
|
await Promise.all(savePromises);
|
|
108
229
|
}
|
|
109
230
|
|
|
@@ -112,15 +233,27 @@ class MultiFileAuthState extends EventEmitter {
|
|
|
112
233
|
if (this.data.creds) promises.push(this._writeFile('creds', this.data.creds));
|
|
113
234
|
if (this.data.device) promises.push(this._writeFile('device', this.data.device));
|
|
114
235
|
if (this.data.cookies) promises.push(this._writeFile('cookies', this.data.cookies));
|
|
236
|
+
// also persist loginHistory and usageStats after creds save
|
|
237
|
+
if (this.data.loginHistory) promises.push(this._writeFile('loginHistory', this.data.loginHistory));
|
|
238
|
+
if (this.data.usageStats) promises.push(this._writeFile('usageStats', this.data.usageStats));
|
|
115
239
|
await Promise.all(promises);
|
|
116
240
|
this.emit('creds-saved');
|
|
117
241
|
}
|
|
118
242
|
|
|
119
243
|
async saveMqttState() {
|
|
244
|
+
// Save base mqtt data
|
|
120
245
|
const promises = [];
|
|
121
246
|
if (this.data.mqttSession) promises.push(this._writeFile('mqttSession', this.data.mqttSession));
|
|
122
247
|
if (this.data.subscriptions) promises.push(this._writeFile('subscriptions', this.data.subscriptions));
|
|
123
248
|
if (this.data.seqIds) promises.push(this._writeFile('seqIds', this.data.seqIds));
|
|
249
|
+
|
|
250
|
+
// Save extended mqtt/realtime data (new files)
|
|
251
|
+
if (this.data.irisState) promises.push(this._writeFile('irisState', this.data.irisState));
|
|
252
|
+
if (this.data.mqttTopics) promises.push(this._writeFile('mqttTopics', this.data.mqttTopics));
|
|
253
|
+
if (this.data.mqttCapabilities) promises.push(this._writeFile('mqttCapabilities', this.data.mqttCapabilities));
|
|
254
|
+
if (this.data.mqttAuth) promises.push(this._writeFile('mqttAuth', this.data.mqttAuth));
|
|
255
|
+
if (this.data.lastPublishIds) promises.push(this._writeFile('lastPublishIds', this.data.lastPublishIds));
|
|
256
|
+
|
|
124
257
|
await Promise.all(promises);
|
|
125
258
|
this.emit('mqtt-state-saved');
|
|
126
259
|
}
|
|
@@ -133,23 +266,35 @@ class MultiFileAuthState extends EventEmitter {
|
|
|
133
266
|
|
|
134
267
|
_debouncedSave(keys) {
|
|
135
268
|
keys.forEach(k => this._dirty.add(k));
|
|
136
|
-
|
|
269
|
+
|
|
137
270
|
if (this._saveDebounceTimer) {
|
|
138
271
|
clearTimeout(this._saveDebounceTimer);
|
|
139
272
|
}
|
|
140
|
-
|
|
273
|
+
|
|
141
274
|
this._saveDebounceTimer = setTimeout(async () => {
|
|
142
275
|
const toSave = Array.from(this._dirty);
|
|
143
276
|
this._dirty.clear();
|
|
144
|
-
|
|
277
|
+
|
|
145
278
|
for (const key of toSave) {
|
|
146
279
|
if (this.data[key] !== null && this.data[key] !== undefined) {
|
|
147
|
-
|
|
280
|
+
try {
|
|
281
|
+
// ensure backup for critical files
|
|
282
|
+
if (['creds', 'cookies', 'device'].includes(key)) {
|
|
283
|
+
await this._createBackup(key);
|
|
284
|
+
}
|
|
285
|
+
await this._writeFile(key, this.data[key]);
|
|
286
|
+
} catch (e) {
|
|
287
|
+
console.error(`[MultiFileAuthState] Debounced write error for ${key}:`, e.message);
|
|
288
|
+
// increment usageStats.errors
|
|
289
|
+
try { this.incrementStat('errors'); } catch (e2) {}
|
|
290
|
+
}
|
|
148
291
|
}
|
|
149
292
|
}
|
|
293
|
+
this.emit('debounced-save-complete', toSave);
|
|
150
294
|
}, this._saveDebounceMs);
|
|
151
295
|
}
|
|
152
296
|
|
|
297
|
+
// --- Setters that schedule debounced saves ---
|
|
153
298
|
setCreds(creds) {
|
|
154
299
|
this.data.creds = creds;
|
|
155
300
|
this._debouncedSave(['creds']);
|
|
@@ -185,6 +330,59 @@ class MultiFileAuthState extends EventEmitter {
|
|
|
185
330
|
this._debouncedSave(['appState']);
|
|
186
331
|
}
|
|
187
332
|
|
|
333
|
+
// --- New setters for additional MQTT files ---
|
|
334
|
+
setIrisState(irisState) {
|
|
335
|
+
this.data.irisState = irisState;
|
|
336
|
+
this._debouncedSave(['irisState']);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
setMqttTopics(topics) {
|
|
340
|
+
this.data.mqttTopics = topics;
|
|
341
|
+
this._debouncedSave(['mqttTopics']);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
setMqttCapabilities(capabilities) {
|
|
345
|
+
this.data.mqttCapabilities = capabilities;
|
|
346
|
+
this._debouncedSave(['mqttCapabilities']);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
setMqttAuth(auth) {
|
|
350
|
+
this.data.mqttAuth = auth;
|
|
351
|
+
this._debouncedSave(['mqttAuth']);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
setLastPublishIds(lastPublishIds) {
|
|
355
|
+
this.data.lastPublishIds = lastPublishIds;
|
|
356
|
+
this._debouncedSave(['lastPublishIds']);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// --- Utility setters ---
|
|
360
|
+
addLoginHistory(entry) {
|
|
361
|
+
if (!this.data.loginHistory) this.data.loginHistory = [];
|
|
362
|
+
this.data.loginHistory.unshift(entry);
|
|
363
|
+
// keep bounded history length
|
|
364
|
+
if (this.data.loginHistory.length > 200) this.data.loginHistory.length = 200;
|
|
365
|
+
this._debouncedSave(['loginHistory']);
|
|
366
|
+
this.emit('login-attempt', entry);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
incrementStat(statName, by = 1) {
|
|
370
|
+
if (!this.data.usageStats) this.data.usageStats = {
|
|
371
|
+
apiRequests: 0,
|
|
372
|
+
mqttMessages: 0,
|
|
373
|
+
errors: 0,
|
|
374
|
+
reconnects: 0,
|
|
375
|
+
lastReset: new Date().toISOString()
|
|
376
|
+
};
|
|
377
|
+
if (typeof this.data.usageStats[statName] === 'number') {
|
|
378
|
+
this.data.usageStats[statName] += by;
|
|
379
|
+
} else {
|
|
380
|
+
this.data.usageStats[statName] = (this.data.usageStats[statName] || 0) + by;
|
|
381
|
+
}
|
|
382
|
+
this._debouncedSave(['usageStats']);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// --- Getters ---
|
|
188
386
|
getCreds() { return this.data.creds; }
|
|
189
387
|
getDevice() { return this.data.device; }
|
|
190
388
|
getCookies() { return this.data.cookies; }
|
|
@@ -192,13 +390,24 @@ class MultiFileAuthState extends EventEmitter {
|
|
|
192
390
|
getSubscriptions() { return this.data.subscriptions; }
|
|
193
391
|
getSeqIds() { return this.data.seqIds; }
|
|
194
392
|
getAppState() { return this.data.appState; }
|
|
393
|
+
getIrisState() { return this.data.irisState; }
|
|
394
|
+
getMqttTopics() { return this.data.mqttTopics; }
|
|
395
|
+
getMqttCapabilities() { return this.data.mqttCapabilities; }
|
|
396
|
+
getMqttAuth() { return this.data.mqttAuth; }
|
|
397
|
+
getLastPublishIds() { return this.data.lastPublishIds; }
|
|
398
|
+
getLoginHistory() { return this.data.loginHistory || []; }
|
|
399
|
+
getUsageStats() { return this.data.usageStats || {}; }
|
|
400
|
+
getAccountsIndex() { return this.data.accountsIndex || {}; }
|
|
195
401
|
|
|
196
402
|
hasValidSession() {
|
|
197
403
|
return !!(this.data.creds && this.data.cookies && this.data.creds.authorization);
|
|
198
404
|
}
|
|
199
405
|
|
|
200
406
|
hasMqttSession() {
|
|
201
|
-
return !!(
|
|
407
|
+
return !!(
|
|
408
|
+
(this.data.mqttSession && this.data.mqttSession.sessionId) ||
|
|
409
|
+
(this.data.mqttAuth && this.data.mqttAuth.jwt)
|
|
410
|
+
);
|
|
202
411
|
}
|
|
203
412
|
|
|
204
413
|
async clearAll() {
|
|
@@ -211,10 +420,213 @@ class MultiFileAuthState extends EventEmitter {
|
|
|
211
420
|
} catch (e) {}
|
|
212
421
|
this.data[key] = null;
|
|
213
422
|
}
|
|
423
|
+
// clear backups too? leave backups intact but emit event
|
|
424
|
+
this.emit('cleared-all');
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// --- Backup utilities exposed ---
|
|
428
|
+
async listBackups() {
|
|
429
|
+
try {
|
|
430
|
+
const b = this._getBackupFolder();
|
|
431
|
+
if (!fs.existsSync(b)) return [];
|
|
432
|
+
const files = await fs.promises.readdir(b);
|
|
433
|
+
return files.map(f => path.join(b, f));
|
|
434
|
+
} catch (e) {
|
|
435
|
+
return [];
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// --- Device fingerprint helpers ---
|
|
440
|
+
computeDeviceFingerprint(deviceObj) {
|
|
441
|
+
try {
|
|
442
|
+
const s = JSON.stringify(deviceObj || this.data.device || {});
|
|
443
|
+
return crypto.createHash('sha256').update(s).digest('hex');
|
|
444
|
+
} catch (e) {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// lock device fingerprint: saves hash into device.json (device.locked = true)
|
|
450
|
+
async lockDeviceFingerprint() {
|
|
451
|
+
try {
|
|
452
|
+
if (!this.data.device) this.data.device = {};
|
|
453
|
+
const hash = this.computeDeviceFingerprint(this.data.device);
|
|
454
|
+
this.data.device.fingerprintHash = hash;
|
|
455
|
+
this.data.device.locked = true;
|
|
456
|
+
await this._writeFile('device', this.data.device);
|
|
457
|
+
this.emit('device-locked', { fingerprintHash: hash });
|
|
458
|
+
return hash;
|
|
459
|
+
} catch (e) {
|
|
460
|
+
console.warn('[MultiFileAuthState] lockDeviceFingerprint failed:', e.message);
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
verifyDeviceFingerprint(deviceObj) {
|
|
466
|
+
try {
|
|
467
|
+
const stored = this.data.device?.fingerprintHash || null;
|
|
468
|
+
if (!stored) return true; // no lock present
|
|
469
|
+
const hash = this.computeDeviceFingerprint(deviceObj || this.data.device);
|
|
470
|
+
return stored === hash;
|
|
471
|
+
} catch (e) {
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// --- Login history helpers ---
|
|
477
|
+
recordLoginAttempt({ success, ip, userAgent, method = 'password', reason = null }) {
|
|
478
|
+
const entry = {
|
|
479
|
+
date: new Date().toISOString(),
|
|
480
|
+
success: Boolean(success),
|
|
481
|
+
ip: ip || null,
|
|
482
|
+
userAgent: userAgent || null,
|
|
483
|
+
method,
|
|
484
|
+
reason
|
|
485
|
+
};
|
|
486
|
+
this.addLoginHistory(entry);
|
|
487
|
+
return entry;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// mark account restricted / checkpoint
|
|
491
|
+
markAccountRestricted({ status = 'checkpoint', reason = null }) {
|
|
492
|
+
if (!this.data.creds) this.data.creds = {};
|
|
493
|
+
this.data.creds.accountStatus = status;
|
|
494
|
+
this.data.creds.restrictedAt = new Date().toISOString();
|
|
495
|
+
this.data.creds.lastError = reason;
|
|
496
|
+
this._debouncedSave(['creds']);
|
|
497
|
+
this.emit('account-restricted', { status, reason });
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// clear restriction
|
|
501
|
+
clearAccountRestriction() {
|
|
502
|
+
if (this.data.creds) {
|
|
503
|
+
delete this.data.creds.accountStatus;
|
|
504
|
+
delete this.data.creds.restrictedAt;
|
|
505
|
+
delete this.data.creds.lastError;
|
|
506
|
+
this._debouncedSave(['creds']);
|
|
507
|
+
this.emit('account-unrestricted');
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// --- Health check ---
|
|
512
|
+
getHealth() {
|
|
513
|
+
return {
|
|
514
|
+
validSession: this.hasValidSession(),
|
|
515
|
+
hasCookies: !!this.data.cookies,
|
|
516
|
+
hasMqtt: this.hasMqttSession(),
|
|
517
|
+
lastLogin: (this.data.loginHistory && this.data.loginHistory[0]?.date) || null,
|
|
518
|
+
usageStats: this.getUsageStats(),
|
|
519
|
+
accountStatus: this.data.creds?.accountStatus || 'ok',
|
|
520
|
+
deviceLocked: !!this.data.device?.locked
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Helper: try to safely extract cookieJar serialization if exists
|
|
526
|
+
async function trySerializeCookieJar(igState) {
|
|
527
|
+
let cookies = null;
|
|
528
|
+
try {
|
|
529
|
+
if (igState.cookieJar && typeof igState.serializeCookieJar === 'function') {
|
|
530
|
+
cookies = await igState.serializeCookieJar();
|
|
531
|
+
} else if (igState.cookieJar && typeof igState.cookieJar.serialize === 'function') {
|
|
532
|
+
// fallback: tough-cookie jar serialize
|
|
533
|
+
const ser = await util.promisify(igState.cookieJar.serialize).bind(igState.cookieJar)();
|
|
534
|
+
cookies = ser;
|
|
535
|
+
}
|
|
536
|
+
} catch (e) {
|
|
537
|
+
console.warn('[MultiFileAuthState] Could not serialize cookies:', e.message);
|
|
538
|
+
}
|
|
539
|
+
return cookies;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Helper: try to read a cookie value from a tough-cookie Jar or equivalent
|
|
543
|
+
async function getCookieValueFromJar(cookieJar, name) {
|
|
544
|
+
if (!cookieJar) return null;
|
|
545
|
+
|
|
546
|
+
// try getCookieString (tough-cookie)
|
|
547
|
+
try {
|
|
548
|
+
if (typeof cookieJar.getCookieString === 'function') {
|
|
549
|
+
const getCookieString = util.promisify(cookieJar.getCookieString).bind(cookieJar);
|
|
550
|
+
const urls = ['https://www.instagram.com', 'https://instagram.com', 'https://i.instagram.com'];
|
|
551
|
+
for (const url of urls) {
|
|
552
|
+
try {
|
|
553
|
+
const cookieString = await getCookieString(url);
|
|
554
|
+
if (cookieString && typeof cookieString === 'string') {
|
|
555
|
+
const pairs = cookieString.split(';').map(s => s.trim());
|
|
556
|
+
for (const p of pairs) {
|
|
557
|
+
const [k, ...rest] = p.split('=');
|
|
558
|
+
if (k === name) return rest.join('=');
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
} catch (e) {
|
|
562
|
+
// ignore and try next url
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
} catch (e) {
|
|
567
|
+
// ignore
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// try getCookies which returns Cookie objects
|
|
571
|
+
try {
|
|
572
|
+
if (typeof cookieJar.getCookies === 'function') {
|
|
573
|
+
const getCookies = util.promisify(cookieJar.getCookies).bind(cookieJar);
|
|
574
|
+
const urls = ['https://www.instagram.com', 'https://instagram.com', 'https://i.instagram.com'];
|
|
575
|
+
for (const url of urls) {
|
|
576
|
+
try {
|
|
577
|
+
const cookies = await getCookies(url);
|
|
578
|
+
if (Array.isArray(cookies)) {
|
|
579
|
+
for (const c of cookies) {
|
|
580
|
+
// cookie object shapes vary: check common keys
|
|
581
|
+
const key = c.key ?? c.name ?? c.name;
|
|
582
|
+
const val = c.value ?? c.value ?? c.value;
|
|
583
|
+
if (key === name) return val;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
} catch (e) {
|
|
587
|
+
// ignore and try next url
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
} catch (e) {
|
|
592
|
+
// ignore
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// try serialized cookie jar structure if present
|
|
596
|
+
try {
|
|
597
|
+
if (cookieJar && typeof cookieJar === 'object' && cookieJar.cookies && Array.isArray(cookieJar.cookies)) {
|
|
598
|
+
const found = cookieJar.cookies.find(c => (c.key === name || c.name === name || c.name === name));
|
|
599
|
+
if (found) return found.value ?? found.value;
|
|
600
|
+
}
|
|
601
|
+
} catch (e) {
|
|
602
|
+
// ignore
|
|
214
603
|
}
|
|
604
|
+
|
|
605
|
+
return null;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Extract cookie-derived fields (sessionid, csrftoken, ds_user_id, mid)
|
|
609
|
+
async function extractCookieFields(igState) {
|
|
610
|
+
const out = {
|
|
611
|
+
sessionIdCookie: null,
|
|
612
|
+
csrfTokenCookie: null,
|
|
613
|
+
dsUserIdCookie: null,
|
|
614
|
+
midCookie: null
|
|
615
|
+
};
|
|
616
|
+
try {
|
|
617
|
+
const cookieJar = igState.cookieJar;
|
|
618
|
+
out.sessionIdCookie = await getCookieValueFromJar(cookieJar, 'sessionid');
|
|
619
|
+
out.csrfTokenCookie = await getCookieValueFromJar(cookieJar, 'csrftoken');
|
|
620
|
+
out.dsUserIdCookie = await getCookieValueFromJar(cookieJar, 'ds_user_id') || await getCookieValueFromJar(cookieJar, 'ds_user');
|
|
621
|
+
out.midCookie = await getCookieValueFromJar(cookieJar, 'mid');
|
|
622
|
+
} catch (e) {
|
|
623
|
+
// ignore
|
|
624
|
+
}
|
|
625
|
+
return out;
|
|
215
626
|
}
|
|
216
627
|
|
|
217
628
|
async function extractStateData(igState) {
|
|
629
|
+
// Basic creds (existing)
|
|
218
630
|
const creds = {
|
|
219
631
|
authorization: igState.authorization || null,
|
|
220
632
|
igWWWClaim: igState.igWWWClaim || null,
|
|
@@ -222,6 +634,7 @@ async function extractStateData(igState) {
|
|
|
222
634
|
passwordEncryptionPubKey: igState.passwordEncryptionPubKey || null
|
|
223
635
|
};
|
|
224
636
|
|
|
637
|
+
// Device remains same
|
|
225
638
|
const device = {
|
|
226
639
|
deviceString: igState.deviceString || null,
|
|
227
640
|
deviceId: igState.deviceId || null,
|
|
@@ -231,15 +644,10 @@ async function extractStateData(igState) {
|
|
|
231
644
|
build: igState.build || null
|
|
232
645
|
};
|
|
233
646
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (igState.cookieJar && typeof igState.serializeCookieJar === 'function') {
|
|
237
|
-
cookies = await igState.serializeCookieJar();
|
|
238
|
-
}
|
|
239
|
-
} catch (e) {
|
|
240
|
-
console.warn('[MultiFileAuthState] Could not serialize cookies:', e.message);
|
|
241
|
-
}
|
|
647
|
+
// Try to serialize cookies (kept separate)
|
|
648
|
+
const cookies = await trySerializeCookieJar(igState);
|
|
242
649
|
|
|
650
|
+
// App state remains same
|
|
243
651
|
const appState = {
|
|
244
652
|
language: igState.language || 'en_US',
|
|
245
653
|
timezoneOffset: igState.timezoneOffset || null,
|
|
@@ -249,6 +657,62 @@ async function extractStateData(igState) {
|
|
|
249
657
|
challenge: igState.challenge || null
|
|
250
658
|
};
|
|
251
659
|
|
|
660
|
+
// --- NEW: richer creds fields derived from igState + cookies ---
|
|
661
|
+
// try to obtain cookie fields
|
|
662
|
+
const cookieFields = await extractCookieFields(igState);
|
|
663
|
+
|
|
664
|
+
// Primary identifiers
|
|
665
|
+
const userIdFromState = igState.cookieUserId || igState.userId || igState.user_id || null;
|
|
666
|
+
const dsUserIdFromCookies = cookieFields.dsUserIdCookie || igState.dsUserId || igState.ds_user_id || null;
|
|
667
|
+
const sessionIdFromCookies = cookieFields.sessionIdCookie || null;
|
|
668
|
+
const csrfFromCookies = cookieFields.csrfTokenCookie || null;
|
|
669
|
+
const midFromCookies = cookieFields.midCookie || igState.mid || null;
|
|
670
|
+
|
|
671
|
+
// username if exposed on state
|
|
672
|
+
const usernameFromState = igState.username || igState.userName || igState.user?.username || null;
|
|
673
|
+
|
|
674
|
+
// rankToken: if userId + uuid available, form `${userId}_${uuid}`
|
|
675
|
+
let rankToken = null;
|
|
676
|
+
try {
|
|
677
|
+
if (userIdFromState && (igState.uuid || device.uuid)) {
|
|
678
|
+
rankToken = `${userIdFromState}_${igState.uuid || device.uuid}`;
|
|
679
|
+
} else if (igState.rankToken) {
|
|
680
|
+
rankToken = igState.rankToken;
|
|
681
|
+
}
|
|
682
|
+
} catch (e) {
|
|
683
|
+
rankToken = igState.rankToken || null;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// sessionId and csrfToken - prefer cookies, fallback to state fields if present
|
|
687
|
+
const sessionId = sessionIdFromCookies || igState.sessionId || igState.sessionid || null;
|
|
688
|
+
const csrfToken = csrfFromCookies || igState.csrfToken || igState.csrftoken || null;
|
|
689
|
+
|
|
690
|
+
// mid fallback
|
|
691
|
+
const mid = midFromCookies || igState.mid || null;
|
|
692
|
+
|
|
693
|
+
// isLoggedIn heuristic: presence of sessionid cookie or an 'isLoggedIn' flag or authorization header
|
|
694
|
+
const isLoggedIn = Boolean(
|
|
695
|
+
sessionId ||
|
|
696
|
+
igState.isLoggedIn ||
|
|
697
|
+
(creds.authorization && creds.authorization.length)
|
|
698
|
+
);
|
|
699
|
+
|
|
700
|
+
// loginAt / lastValidatedAt: try to take from igState if present, otherwise null
|
|
701
|
+
const loginAt = igState.loginAt || igState.loggedInAt || null;
|
|
702
|
+
const lastValidatedAt = igState.lastValidatedAt || igState.lastValidated || null;
|
|
703
|
+
|
|
704
|
+
// Attach all new fields into creds object (non-destructive)
|
|
705
|
+
creds.userId = userIdFromState || dsUserIdFromCookies || creds.userId || null;
|
|
706
|
+
creds.dsUserId = dsUserIdFromCookies || null;
|
|
707
|
+
creds.username = usernameFromState || null;
|
|
708
|
+
creds.rankToken = rankToken;
|
|
709
|
+
creds.sessionId = sessionId;
|
|
710
|
+
creds.csrfToken = csrfToken;
|
|
711
|
+
creds.mid = mid;
|
|
712
|
+
creds.isLoggedIn = isLoggedIn;
|
|
713
|
+
creds.loginAt = loginAt;
|
|
714
|
+
creds.lastValidatedAt = lastValidatedAt;
|
|
715
|
+
|
|
252
716
|
return { creds, device, cookies, appState };
|
|
253
717
|
}
|
|
254
718
|
|
|
@@ -260,7 +724,25 @@ async function applyStateData(igState, authState) {
|
|
|
260
724
|
if (creds.igWWWClaim) igState.igWWWClaim = creds.igWWWClaim;
|
|
261
725
|
if (creds.passwordEncryptionKeyId) igState.passwordEncryptionKeyId = creds.passwordEncryptionKeyId;
|
|
262
726
|
if (creds.passwordEncryptionPubKey) igState.passwordEncryptionPubKey = creds.passwordEncryptionPubKey;
|
|
263
|
-
igState
|
|
727
|
+
// if igState provides a helper to update authorization, call it
|
|
728
|
+
if (typeof igState.updateAuthorization === 'function') {
|
|
729
|
+
try { igState.updateAuthorization(); } catch (e) {}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// apply new creds-derived fields if state supports them (non-intrusive)
|
|
733
|
+
try {
|
|
734
|
+
if (creds.userId && !igState.cookieUserId) igState.cookieUserId = creds.userId;
|
|
735
|
+
if (creds.username && !igState.username) igState.username = creds.username;
|
|
736
|
+
if (creds.rankToken && !igState.rankToken) igState.rankToken = creds.rankToken;
|
|
737
|
+
if (creds.sessionId && !igState.sessionId) igState.sessionId = creds.sessionId;
|
|
738
|
+
if (creds.csrfToken && !igState.csrfToken) igState.csrfToken = creds.csrfToken;
|
|
739
|
+
if (creds.mid && !igState.mid) igState.mid = creds.mid;
|
|
740
|
+
if (typeof creds.isLoggedIn !== 'undefined' && !igState.isLoggedIn) igState.isLoggedIn = creds.isLoggedIn;
|
|
741
|
+
if (creds.loginAt && !igState.loginAt) igState.loginAt = creds.loginAt;
|
|
742
|
+
if (creds.lastValidatedAt && !igState.lastValidatedAt) igState.lastValidatedAt = creds.lastValidatedAt;
|
|
743
|
+
} catch (e) {
|
|
744
|
+
// ignore if igState shape different
|
|
745
|
+
}
|
|
264
746
|
}
|
|
265
747
|
|
|
266
748
|
if (device) {
|
|
@@ -274,7 +756,15 @@ async function applyStateData(igState, authState) {
|
|
|
274
756
|
|
|
275
757
|
if (cookies) {
|
|
276
758
|
try {
|
|
277
|
-
|
|
759
|
+
if (typeof igState.deserializeCookieJar === 'function') {
|
|
760
|
+
await igState.deserializeCookieJar(cookies);
|
|
761
|
+
} else if (igState.cookieJar && typeof igState.cookieJar.restore === 'function') {
|
|
762
|
+
// fallback restore
|
|
763
|
+
await util.promisify(igState.cookieJar.restore).bind(igState.cookieJar)(cookies);
|
|
764
|
+
} else if (igState.cookieJar && typeof igState.cookieJar._importCookies === 'function') {
|
|
765
|
+
// last-resort, not likely
|
|
766
|
+
try { igState.cookieJar._importCookies(cookies); } catch (e) {}
|
|
767
|
+
}
|
|
278
768
|
} catch (e) {
|
|
279
769
|
console.warn('[MultiFileAuthState] Could not deserialize cookies:', e.message);
|
|
280
770
|
}
|
|
@@ -290,26 +780,46 @@ async function applyStateData(igState, authState) {
|
|
|
290
780
|
}
|
|
291
781
|
}
|
|
292
782
|
|
|
783
|
+
// Main exported helper that wires MultiFileAuthState to your clients
|
|
293
784
|
async function useMultiFileAuthState(folder) {
|
|
294
785
|
const authState = new MultiFileAuthState(folder);
|
|
295
786
|
await authState.loadAll();
|
|
296
787
|
|
|
788
|
+
// Helper: persist additional derived creds fields when saving creds
|
|
789
|
+
const enrichAndSaveCreds = async (igClientState) => {
|
|
790
|
+
const { creds, device, cookies, appState } = await extractStateData(igClientState);
|
|
791
|
+
// merge with existing creds non-destructive
|
|
792
|
+
authState.data.creds = Object.assign({}, authState.data.creds || {}, creds);
|
|
793
|
+
authState.data.device = Object.assign({}, authState.data.device || {}, device);
|
|
794
|
+
authState.data.cookies = cookies || authState.data.cookies;
|
|
795
|
+
authState.data.appState = Object.assign({}, authState.data.appState || {}, appState);
|
|
796
|
+
await authState.saveCreds();
|
|
797
|
+
await authState.saveAppState();
|
|
798
|
+
};
|
|
799
|
+
|
|
297
800
|
const saveCreds = async (igClient) => {
|
|
298
801
|
if (!igClient || !igClient.state) {
|
|
299
802
|
console.warn('[useMultiFileAuthState] No igClient provided to saveCreds');
|
|
300
803
|
return;
|
|
301
804
|
}
|
|
302
805
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
806
|
+
// Use the enriched saver
|
|
807
|
+
await enrichAndSaveCreds(igClient.state);
|
|
808
|
+
|
|
809
|
+
// Also attempt to extract some cookie-derived fields for convenience
|
|
810
|
+
try {
|
|
811
|
+
const cookieFields = await extractCookieFields(igClient.state);
|
|
812
|
+
if (cookieFields.sessionIdCookie) {
|
|
813
|
+
if (!authState.data.creds) authState.data.creds = {};
|
|
814
|
+
authState.data.creds.sessionId = cookieFields.sessionIdCookie;
|
|
815
|
+
}
|
|
816
|
+
if (cookieFields.dsUserIdCookie) {
|
|
817
|
+
if (!authState.data.creds) authState.data.creds = {};
|
|
818
|
+
authState.data.creds.dsUserId = cookieFields.dsUserIdCookie;
|
|
819
|
+
}
|
|
820
|
+
authState._debouncedSave(['creds']);
|
|
821
|
+
} catch (e) {}
|
|
822
|
+
|
|
313
823
|
console.log('[useMultiFileAuthState] Credentials saved to', folder);
|
|
314
824
|
};
|
|
315
825
|
|
|
@@ -319,6 +829,7 @@ async function useMultiFileAuthState(folder) {
|
|
|
319
829
|
return;
|
|
320
830
|
}
|
|
321
831
|
|
|
832
|
+
// base mqtt session
|
|
322
833
|
const mqttSession = {
|
|
323
834
|
sessionId: null,
|
|
324
835
|
mqttSessionId: null,
|
|
@@ -328,30 +839,92 @@ async function useMultiFileAuthState(folder) {
|
|
|
328
839
|
|
|
329
840
|
try {
|
|
330
841
|
if (realtimeClient.ig && realtimeClient.ig.state) {
|
|
331
|
-
mqttSession.userId = realtimeClient.ig.state.cookieUserId;
|
|
332
|
-
|
|
842
|
+
mqttSession.userId = realtimeClient.ig.state.cookieUserId || realtimeClient.ig.state.userId || null;
|
|
843
|
+
// attempt to extract sessionId from JWT helper if available
|
|
844
|
+
mqttSession.sessionId = (typeof realtimeClient.extractSessionIdFromJWT === 'function') ? realtimeClient.extractSessionIdFromJWT() : null;
|
|
333
845
|
}
|
|
334
846
|
if (realtimeClient.connection) {
|
|
335
847
|
mqttSession.mqttSessionId = realtimeClient.connection?.clientInfo?.clientMqttSessionId?.toString() || null;
|
|
336
848
|
}
|
|
337
|
-
} catch (e) {
|
|
849
|
+
} catch (e) {
|
|
850
|
+
// ignore
|
|
851
|
+
}
|
|
338
852
|
|
|
853
|
+
// subscriptions
|
|
339
854
|
const subscriptions = {
|
|
340
855
|
graphQlSubs: realtimeClient.initOptions?.graphQlSubs || [],
|
|
341
856
|
skywalkerSubs: realtimeClient.initOptions?.skywalkerSubs || [],
|
|
342
857
|
subscribedAt: new Date().toISOString()
|
|
343
858
|
};
|
|
344
859
|
|
|
860
|
+
// seq ids (iris)
|
|
345
861
|
const seqIds = {};
|
|
346
862
|
if (realtimeClient.initOptions?.irisData) {
|
|
347
863
|
seqIds.seq_id = realtimeClient.initOptions.irisData.seq_id || null;
|
|
348
864
|
seqIds.snapshot_at_ms = realtimeClient.initOptions.irisData.snapshot_at_ms || null;
|
|
349
865
|
}
|
|
350
866
|
|
|
867
|
+
// --- Extended MQTT / realtime info (new) ---
|
|
868
|
+
// irisState: things like subscription_id / device id / other iris metadata
|
|
869
|
+
const irisState = {};
|
|
870
|
+
try {
|
|
871
|
+
// try to glean from various possible places on realtimeClient
|
|
872
|
+
irisState.subscription_id = realtimeClient.initOptions?.irisData?.subscription_id || realtimeClient.iris?.subscriptionId || realtimeClient.irisState?.subscription_id || null;
|
|
873
|
+
irisState.user_id = realtimeClient.ig?.state?.cookieUserId || realtimeClient.ig?.state?.userId || irisState.user_id || null;
|
|
874
|
+
irisState.device_id = realtimeClient.connection?.clientInfo?.deviceId || realtimeClient.initOptions?.deviceId || null;
|
|
875
|
+
irisState.created_at = new Date().toISOString();
|
|
876
|
+
} catch (e) {}
|
|
877
|
+
|
|
878
|
+
// mqttTopics: topics observed/subscribed
|
|
879
|
+
const mqttTopics = {};
|
|
880
|
+
try {
|
|
881
|
+
mqttTopics.topics = realtimeClient.connection?.subscribedTopics || realtimeClient._subscribedTopics || realtimeClient.subscribedTopics || [];
|
|
882
|
+
mqttTopics.updatedAt = new Date().toISOString();
|
|
883
|
+
} catch (e) {
|
|
884
|
+
mqttTopics.topics = [];
|
|
885
|
+
mqttTopics.updatedAt = new Date().toISOString();
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// mqttCapabilities: features supported / protocol version
|
|
889
|
+
const mqttCapabilities = {};
|
|
890
|
+
try {
|
|
891
|
+
mqttCapabilities.supportsTyping = Boolean(realtimeClient.initOptions?.supportsTyping ?? realtimeClient.capabilities?.supportsTyping);
|
|
892
|
+
mqttCapabilities.supportsReactions = Boolean(realtimeClient.initOptions?.supportsReactions ?? realtimeClient.capabilities?.supportsReactions);
|
|
893
|
+
mqttCapabilities.supportsVoice = Boolean(realtimeClient.initOptions?.supportsVoice ?? realtimeClient.capabilities?.supportsVoice);
|
|
894
|
+
mqttCapabilities.protocolVersion = realtimeClient.connection?.protocolVersion || realtimeClient.initOptions?.protocolVersion || null;
|
|
895
|
+
mqttCapabilities.collectedAt = new Date().toISOString();
|
|
896
|
+
} catch (e) {}
|
|
897
|
+
|
|
898
|
+
// mqttAuth: if realtimeClient keeps an auth token / jwt for mqtt, store (but be careful: short lived)
|
|
899
|
+
const mqttAuth = {};
|
|
900
|
+
try {
|
|
901
|
+
mqttAuth.jwt = realtimeClient.connection?.authToken || realtimeClient.mqttAuth?.jwt || realtimeClient.initOptions?.mqttJwt || null;
|
|
902
|
+
mqttAuth.expiresAt = realtimeClient.connection?.authExpiresAt || realtimeClient.mqttAuth?.expiresAt || null;
|
|
903
|
+
mqttAuth.collectedAt = new Date().toISOString();
|
|
904
|
+
} catch (e) {}
|
|
905
|
+
|
|
906
|
+
// lastPublishIds: tracking last message ids for reliable publish/acks
|
|
907
|
+
const lastPublishIds = {};
|
|
908
|
+
try {
|
|
909
|
+
lastPublishIds.lastMessageId = realtimeClient._lastMessageId || realtimeClient.lastMessageId || null;
|
|
910
|
+
lastPublishIds.lastAckedAt = realtimeClient._lastAckedAt || null;
|
|
911
|
+
lastPublishIds.collectedAt = new Date().toISOString();
|
|
912
|
+
} catch (e) {}
|
|
913
|
+
|
|
914
|
+
// Assign all into authState and persist
|
|
351
915
|
authState.data.mqttSession = mqttSession;
|
|
352
916
|
authState.data.subscriptions = subscriptions;
|
|
353
917
|
authState.data.seqIds = seqIds;
|
|
354
918
|
|
|
919
|
+
authState.data.irisState = irisState;
|
|
920
|
+
authState.data.mqttTopics = mqttTopics;
|
|
921
|
+
authState.data.mqttCapabilities = mqttCapabilities;
|
|
922
|
+
authState.data.mqttAuth = mqttAuth;
|
|
923
|
+
authState.data.lastPublishIds = lastPublishIds;
|
|
924
|
+
|
|
925
|
+
// Increment stats
|
|
926
|
+
authState.incrementStat('mqttMessages');
|
|
927
|
+
|
|
355
928
|
await authState.saveMqttState();
|
|
356
929
|
console.log('[useMultiFileAuthState] MQTT session saved to', folder);
|
|
357
930
|
};
|
|
@@ -379,6 +952,7 @@ async function useMultiFileAuthState(folder) {
|
|
|
379
952
|
|
|
380
953
|
const subs = authState.getSubscriptions() || {};
|
|
381
954
|
const seqIds = authState.getSeqIds() || {};
|
|
955
|
+
const mqttAuth = authState.getMqttAuth() || null;
|
|
382
956
|
|
|
383
957
|
return {
|
|
384
958
|
graphQlSubs: subs.graphQlSubs || ['ig_sub_direct', 'ig_sub_direct_v2_message_sync'],
|
|
@@ -386,7 +960,9 @@ async function useMultiFileAuthState(folder) {
|
|
|
386
960
|
irisData: seqIds.seq_id ? {
|
|
387
961
|
seq_id: seqIds.seq_id,
|
|
388
962
|
snapshot_at_ms: seqIds.snapshot_at_ms
|
|
389
|
-
} : null
|
|
963
|
+
} : null,
|
|
964
|
+
mqttAuthToken: mqttAuth?.jwt || null,
|
|
965
|
+
mqttAuthExpiresAt: mqttAuth?.expiresAt || null
|
|
390
966
|
};
|
|
391
967
|
};
|
|
392
968
|
|
|
@@ -400,32 +976,182 @@ async function useMultiFileAuthState(folder) {
|
|
|
400
976
|
if (!igClient) return true;
|
|
401
977
|
|
|
402
978
|
try {
|
|
403
|
-
|
|
979
|
+
// quick check by calling account/currentUser or equivalent
|
|
980
|
+
if (igClient.account && typeof igClient.account.currentUser === 'function') {
|
|
981
|
+
await igClient.account.currentUser();
|
|
982
|
+
return true;
|
|
983
|
+
}
|
|
984
|
+
// fallback assume valid if we have creds - up to caller to verify
|
|
404
985
|
return true;
|
|
405
986
|
} catch (e) {
|
|
406
987
|
console.warn('[useMultiFileAuthState] Session validation failed:', e.message);
|
|
988
|
+
// emit session-expired if credentials appear invalid
|
|
989
|
+
authState.emit('session-expired', { reason: e.message });
|
|
407
990
|
return false;
|
|
408
991
|
}
|
|
409
992
|
};
|
|
410
993
|
|
|
994
|
+
// --- Auto-refresh / revalidation helpers ---
|
|
995
|
+
const _autoRefreshCheck = async () => {
|
|
996
|
+
try {
|
|
997
|
+
if (!authState.data.creds) return;
|
|
998
|
+
// If creds indicate expiry timestamp, check it
|
|
999
|
+
const expiresAt = authState.data.creds.sessionExpiresAt ? new Date(authState.data.creds.sessionExpiresAt).getTime() : null;
|
|
1000
|
+
if (expiresAt && Date.now() > expiresAt) {
|
|
1001
|
+
authState.data.creds.isExpired = true;
|
|
1002
|
+
authState._debouncedSave(['creds']);
|
|
1003
|
+
authState.emit('session-expired', { reason: 'sessionExpiresAt' });
|
|
1004
|
+
} else {
|
|
1005
|
+
// if igClient provided, optionally validate remote
|
|
1006
|
+
if (authState._autoRefreshClient) {
|
|
1007
|
+
try {
|
|
1008
|
+
const valid = await isSessionValid(authState._autoRefreshClient);
|
|
1009
|
+
if (!valid) {
|
|
1010
|
+
authState.emit('session-expired', { reason: 'remote-check' });
|
|
1011
|
+
} else {
|
|
1012
|
+
// update lastValidatedAt
|
|
1013
|
+
if (!authState.data.creds) authState.data.creds = {};
|
|
1014
|
+
authState.data.creds.lastValidatedAt = new Date().toISOString();
|
|
1015
|
+
authState._debouncedSave(['creds']);
|
|
1016
|
+
}
|
|
1017
|
+
} catch (e) {}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
} catch (e) {
|
|
1021
|
+
// ignore
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
const enableAutoRefresh = (igClient, intervalMs = 5 * 60 * 1000) => {
|
|
1026
|
+
// igClient optional: if provided, will be used to do remote validation
|
|
1027
|
+
try {
|
|
1028
|
+
disableAutoRefresh();
|
|
1029
|
+
authState._autoRefreshClient = igClient || null;
|
|
1030
|
+
authState._autoRefreshIntervalMs = intervalMs;
|
|
1031
|
+
authState._autoRefreshTimer = setInterval(_autoRefreshCheck, intervalMs);
|
|
1032
|
+
// run immediately once
|
|
1033
|
+
_autoRefreshCheck();
|
|
1034
|
+
authState.emit('auto-refresh-enabled', { intervalMs });
|
|
1035
|
+
} catch (e) {
|
|
1036
|
+
console.warn('[useMultiFileAuthState] enableAutoRefresh error:', e.message);
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
|
|
1040
|
+
const disableAutoRefresh = () => {
|
|
1041
|
+
try {
|
|
1042
|
+
if (authState._autoRefreshTimer) {
|
|
1043
|
+
clearInterval(authState._autoRefreshTimer);
|
|
1044
|
+
authState._autoRefreshTimer = null;
|
|
1045
|
+
authState._autoRefreshClient = null;
|
|
1046
|
+
authState.emit('auto-refresh-disabled');
|
|
1047
|
+
}
|
|
1048
|
+
} catch (e) {}
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1051
|
+
// --- Login history / audit helpers ---
|
|
1052
|
+
const recordLoginAttempt = async ({ success, ip = null, userAgent = null, method = 'password', reason = null }) => {
|
|
1053
|
+
const entry = authState.recordLoginAttempt({ success, ip, userAgent, method, reason });
|
|
1054
|
+
// return entry for caller
|
|
1055
|
+
return entry;
|
|
1056
|
+
};
|
|
1057
|
+
|
|
1058
|
+
// --- Account restriction helpers ---
|
|
1059
|
+
const markAccountRestricted = ({ status = 'checkpoint', reason = null }) => {
|
|
1060
|
+
authState.markAccountRestricted({ status, reason });
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
const clearAccountRestriction = () => {
|
|
1064
|
+
authState.clearAccountRestriction();
|
|
1065
|
+
};
|
|
1066
|
+
|
|
1067
|
+
// --- Usage stats helpers ---
|
|
1068
|
+
const incrementStat = (statName, by = 1) => {
|
|
1069
|
+
authState.incrementStat(statName, by);
|
|
1070
|
+
};
|
|
1071
|
+
|
|
1072
|
+
// --- Backup / restore helpers exposed ---
|
|
1073
|
+
const restoreLatestBackup = async (key) => {
|
|
1074
|
+
return await authState.restoreLatestBackup(key);
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
// --- Device fingerprinting ---
|
|
1078
|
+
const lockDeviceFingerprint = async () => {
|
|
1079
|
+
return await authState.lockDeviceFingerprint();
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
const verifyDeviceFingerprint = (deviceObj) => {
|
|
1083
|
+
return authState.verifyDeviceFingerprint(deviceObj);
|
|
1084
|
+
};
|
|
1085
|
+
|
|
1086
|
+
// --- Accounts manager ---
|
|
1087
|
+
const registerAccount = async (name, folderPath) => {
|
|
1088
|
+
if (!authState.data.accountsIndex) authState.data.accountsIndex = {};
|
|
1089
|
+
authState.data.accountsIndex[name] = folderPath;
|
|
1090
|
+
await authState._writeFile('accountsIndex', authState.data.accountsIndex);
|
|
1091
|
+
authState.emit('account-registered', { name, folderPath });
|
|
1092
|
+
return authState.data.accountsIndex;
|
|
1093
|
+
};
|
|
1094
|
+
|
|
1095
|
+
const listAccounts = () => {
|
|
1096
|
+
return authState.getAccountsIndex();
|
|
1097
|
+
};
|
|
1098
|
+
|
|
1099
|
+
// --- Health check ---
|
|
1100
|
+
const getHealth = () => {
|
|
1101
|
+
return authState.getHealth();
|
|
1102
|
+
};
|
|
1103
|
+
|
|
1104
|
+
// --- Misc helpers: loginHistory & usageStats getters ---
|
|
1105
|
+
const getLoginHistory = () => authState.getLoginHistory();
|
|
1106
|
+
const getUsageStats = () => authState.getUsageStats();
|
|
1107
|
+
|
|
411
1108
|
return {
|
|
412
1109
|
state: authState,
|
|
413
|
-
|
|
1110
|
+
|
|
414
1111
|
saveCreds,
|
|
415
1112
|
loadCreds,
|
|
416
|
-
|
|
1113
|
+
|
|
417
1114
|
saveMqttSession,
|
|
418
1115
|
getMqttConnectOptions,
|
|
419
|
-
|
|
1116
|
+
|
|
420
1117
|
clearSession,
|
|
421
1118
|
isSessionValid,
|
|
422
1119
|
|
|
423
1120
|
hasSession: () => authState.hasValidSession(),
|
|
424
1121
|
hasMqttSession: () => authState.hasMqttSession(),
|
|
425
|
-
|
|
1122
|
+
|
|
426
1123
|
folder,
|
|
427
|
-
|
|
428
|
-
|
|
1124
|
+
|
|
1125
|
+
// pass-through for convenience
|
|
1126
|
+
getData: () => authState.data,
|
|
1127
|
+
|
|
1128
|
+
// convenience setters/getters for the new data
|
|
1129
|
+
setIrisState: (s) => authState.setIrisState(s),
|
|
1130
|
+
setMqttTopics: (t) => authState.setMqttTopics(t),
|
|
1131
|
+
setMqttCapabilities: (c) => authState.setMqttCapabilities(c),
|
|
1132
|
+
setMqttAuth: (a) => authState.setMqttAuth(a),
|
|
1133
|
+
setLastPublishIds: (p) => authState.setLastPublishIds(p),
|
|
1134
|
+
getIrisState: () => authState.getIrisState(),
|
|
1135
|
+
getMqttTopics: () => authState.getMqttTopics(),
|
|
1136
|
+
getMqttCapabilities: () => authState.getMqttCapabilities(),
|
|
1137
|
+
getMqttAuth: () => authState.getMqttAuth(),
|
|
1138
|
+
getLastPublishIds: () => authState.getLastPublishIds(),
|
|
1139
|
+
|
|
1140
|
+
// new utilities
|
|
1141
|
+
enableAutoRefresh,
|
|
1142
|
+
disableAutoRefresh,
|
|
1143
|
+
recordLoginAttempt,
|
|
1144
|
+
getLoginHistory,
|
|
1145
|
+
markAccountRestricted,
|
|
1146
|
+
clearAccountRestriction,
|
|
1147
|
+
incrementStat,
|
|
1148
|
+
getUsageStats,
|
|
1149
|
+
restoreLatestBackup,
|
|
1150
|
+
lockDeviceFingerprint,
|
|
1151
|
+
verifyDeviceFingerprint,
|
|
1152
|
+
registerAccount,
|
|
1153
|
+
listAccounts,
|
|
1154
|
+
getHealth
|
|
429
1155
|
};
|
|
430
1156
|
}
|
|
431
1157
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodejs-insta-private-api-mqtt",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.10",
|
|
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": {
|