nodejs-insta-private-api-mqtt 1.1.5 → 1.1.6
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/README.md +14 -5
- package/dist/core/request.js +15 -10
- package/dist/core/state.js +5 -4
- package/dist/mqttot/mqttot.client.js +37 -8
- package/dist/mqttot/mqttot.connection.js +5 -1
- package/dist/realtime/realtime.client.js +60 -425
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,15 +2,24 @@ Dear users,
|
|
|
2
2
|
First of all, when handling view-once images or videos, please make sure to save the received media in a folder such as /storage/emulated/0/Pictures/, or depending on your device's path. The photos and videos are saved correctly on your phone, but the library currently has some issues with uploading them back to Instagram. This will be resolved as soon as possible.
|
|
3
3
|
I post many versions of the project because Instagram changes the protocol almost daily, if you like this project leave a star on github https://github.com/Kunboruto20/nodejs-insta-private-api.git
|
|
4
4
|
|
|
5
|
+
⚠️ IMPORTANT NOTICE
|
|
6
|
+
This is an important announcement regarding the nodejs-insta-private-api library.
|
|
7
|
+
The old repository and NPM package were lost and are now associated with a different NPM account.
|
|
8
|
+
Starting from today, this NPM account will be the official and active source where this library will be published and maintained.
|
|
9
|
+
Please make sure you are using this package from this account only to receive future updates, fixes, and support.
|
|
5
10
|
|
|
11
|
+
You can subscribe to any Instagram topic in this library visit the bookstore code, it would help me a lot if you like this project and if it helps you!
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
If you see clones of nodejs-insta-private-api on npm don't download, it could be malicious code and you may have security issues on your Instagram accounts, always use this project or the nodejs-insta-private-api project but that's on the lost account, always use npm install nodejs-insta-private-api-mqtt in your projects
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# nodejs-insta-private-api-mqtt
|
|
8
17
|
|
|
9
18
|
This project implements a complete and production-ready MQTT protocol client for Instagram's real-time messaging infrastructure. Instagram uses MQTT natively for direct messages, notifications, and real-time presence updates. This library replicates that exact implementation, allowing developers to build high-performance bots and automation tools that communicate with Instagram's backend using the same protocol the official app uses.
|
|
10
19
|
|
|
11
20
|
By leveraging MQTT instead of Instagram's REST API, this library achieves sub-500ms message latency, bidirectional real-time communication, and native support for notifications, presence tracking, and thread management. The implementation is reverse-engineered from Instagram's mobile app protocol and tested extensively for reliability and compatibility.
|
|
12
21
|
|
|
13
|
-
## Features (
|
|
22
|
+
## Features ( - iOS + Android Full Support)
|
|
14
23
|
|
|
15
24
|
- **NEW: FULL iOS SUPPORT** - iPhone 16/15/14/13/12 + iPad Pro/Air device emulation
|
|
16
25
|
- **NEW: FULL ANDROID SUPPORT** - Samsung, Huawei, Google Pixel, OnePlus, Xiaomi, OPPO
|
|
@@ -66,7 +75,7 @@ Requires **Node.js 18 or higher**.
|
|
|
66
75
|
|
|
67
76
|
---
|
|
68
77
|
|
|
69
|
-
## NEW: Custom Device Emulation (
|
|
78
|
+
## NEW: Custom Device Emulation ()
|
|
70
79
|
|
|
71
80
|
**Default Device:** Samsung Galaxy S25 Ultra (Android 15) - used automatically if you don't set a custom device.
|
|
72
81
|
|
|
@@ -84,7 +93,7 @@ This feature allows you to **choose which phone model Instagram sees** when your
|
|
|
84
93
|
The easiest way to set a custom device is using the built-in presets:
|
|
85
94
|
|
|
86
95
|
```javascript
|
|
87
|
-
const { IgApiClient } = require('nodejs-insta-private-api');
|
|
96
|
+
const { IgApiClient } = require('nodejs-insta-private-api-mqtt');
|
|
88
97
|
|
|
89
98
|
const ig = new IgApiClient();
|
|
90
99
|
|
|
@@ -995,7 +1004,7 @@ realtime.on('message', async (data) => {
|
|
|
995
1004
|
### Extract Media URLs Without Downloading
|
|
996
1005
|
|
|
997
1006
|
```javascript
|
|
998
|
-
const { extractMediaUrls } = require('nodejs-insta-private-api');
|
|
1007
|
+
const { extractMediaUrls } = require('nodejs-insta-private-api-mqtt');
|
|
999
1008
|
|
|
1000
1009
|
realtime.on('message', async (data) => {
|
|
1001
1010
|
const msg = data.message;
|
package/dist/core/request.js
CHANGED
|
@@ -13,7 +13,11 @@ class Request {
|
|
|
13
13
|
baseURL: 'https://i.instagram.com/',
|
|
14
14
|
timeout: 30000,
|
|
15
15
|
headers: {
|
|
16
|
-
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
|
16
|
+
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
17
|
+
'Accept-Encoding': 'gzip, deflate',
|
|
18
|
+
'Connection': 'keep-alive',
|
|
19
|
+
'Accept': '*/*',
|
|
20
|
+
'Accept-Language': 'en-US'
|
|
17
21
|
}
|
|
18
22
|
});
|
|
19
23
|
}
|
|
@@ -175,31 +179,32 @@ class Request {
|
|
|
175
179
|
error.response = response;
|
|
176
180
|
error.status = response.status;
|
|
177
181
|
error.data = data;
|
|
182
|
+
// Anti-bot: Randomize delay on error to mimic human reaction time
|
|
178
183
|
return error;
|
|
179
184
|
}
|
|
180
185
|
|
|
181
186
|
getDefaultHeaders() {
|
|
182
187
|
return {
|
|
183
188
|
'User-Agent': this.client.state.appUserAgent,
|
|
184
|
-
'X-Ads-Opt-Out':
|
|
185
|
-
'X-IG-App-Locale':
|
|
186
|
-
'X-IG-Device-Locale':
|
|
189
|
+
'X-Ads-Opt-Out': '0', // Always 0 for humans
|
|
190
|
+
'X-IG-App-Locale': 'en_US',
|
|
191
|
+
'X-IG-Device-Locale': 'en_US',
|
|
187
192
|
'X-Pigeon-Session-Id': this.client.state.pigeonSessionId,
|
|
188
193
|
'X-Pigeon-Rawclienttime': (Date.now() / 1000).toFixed(3),
|
|
189
|
-
'X-IG-Connection-Speed': `${random(
|
|
194
|
+
'X-IG-Connection-Speed': `${random(2000, 4000)}kbps`, // Faster 5G/WiFi speeds
|
|
190
195
|
'X-IG-Bandwidth-Speed-KBPS': '-1.000',
|
|
191
196
|
'X-IG-Bandwidth-TotalBytes-B': '0',
|
|
192
197
|
'X-IG-Bandwidth-TotalTime-MS': '0',
|
|
193
198
|
'X-IG-Extended-CDN-Thumbnail-Cache-Busting-Value': this.client.state.thumbnailCacheBustingValue.toString(),
|
|
194
199
|
'X-Bloks-Version-Id': this.client.state.bloksVersionId,
|
|
195
200
|
'X-IG-WWW-Claim': this.client.state.igWWWClaim || '0',
|
|
196
|
-
'X-Bloks-Is-Layout-RTL':
|
|
197
|
-
'X-IG-Connection-Type':
|
|
198
|
-
'X-IG-Capabilities':
|
|
199
|
-
'X-IG-App-ID':
|
|
201
|
+
'X-Bloks-Is-Layout-RTL': 'false',
|
|
202
|
+
'X-IG-Connection-Type': 'WIFI', // Prefer WIFI for stability
|
|
203
|
+
'X-IG-Capabilities': '3brTv10=', // iOS capabilities
|
|
204
|
+
'X-IG-App-ID': '1217981644879628', // Official iOS App ID
|
|
200
205
|
'X-IG-Device-ID': this.client.state.uuid,
|
|
201
206
|
'X-IG-Android-ID': this.client.state.deviceId,
|
|
202
|
-
'Accept-Language':
|
|
207
|
+
'Accept-Language': 'en-US',
|
|
203
208
|
'X-FB-HTTP-Engine': 'Liger',
|
|
204
209
|
'Authorization': this.client.state.authorization,
|
|
205
210
|
'Host': 'i.instagram.com',
|
package/dist/core/state.js
CHANGED
|
@@ -34,12 +34,12 @@ class State {
|
|
|
34
34
|
this.parsedAuthorization = undefined;
|
|
35
35
|
|
|
36
36
|
// ===== PLATFORM SUPPORT (iOS + Android) =====
|
|
37
|
-
this.platform = '
|
|
37
|
+
this.platform = 'ios'; // 'android' or 'ios'
|
|
38
38
|
this.iosVersion = '18.1';
|
|
39
39
|
this.iosAppVersion = '347.0.0.36.89';
|
|
40
40
|
this.iosAppVersionCode = '618023787';
|
|
41
|
-
this.iosDeviceModel = '
|
|
42
|
-
this.iosDeviceName = 'iPhone';
|
|
41
|
+
this.iosDeviceModel = 'iPhone17,1'; // iPhone 16 Pro Max
|
|
42
|
+
this.iosDeviceName = 'iPhone 16 Pro Max';
|
|
43
43
|
this.iosBundleId = 'com.burbn.instagram';
|
|
44
44
|
|
|
45
45
|
// cookie jar (tough-cookie)
|
|
@@ -101,7 +101,8 @@ class State {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
get iosUserAgent() {
|
|
104
|
-
|
|
104
|
+
// iPhone 16 Pro Max Hardcoded User Agent for Maximum Stealth
|
|
105
|
+
return `Instagram 347.0.0.36.89 (iPhone17,1; iOS 18.1; en_US; en_US; scale=3.00; 1320x2868; 618023787) AppleWebKit/420+`;
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
get iosResolution() {
|
|
@@ -23,12 +23,36 @@ class MQTToTClient extends mqtts_1.MqttClient {
|
|
|
23
23
|
host: options.url,
|
|
24
24
|
port: 443,
|
|
25
25
|
proxyOptions: options.socksOptions,
|
|
26
|
-
additionalOptions:
|
|
26
|
+
additionalOptions: {
|
|
27
|
+
...options.additionalOptions,
|
|
28
|
+
// Anti-Bot: Enforce TLS 1.3 with Unified Cipher Suites (iOS & Android 14/15)
|
|
29
|
+
minVersion: 'TLSv1.3',
|
|
30
|
+
maxVersion: 'TLSv1.3',
|
|
31
|
+
ciphers: [
|
|
32
|
+
'TLS_AES_128_GCM_SHA256',
|
|
33
|
+
'TLS_AES_256_GCM_SHA384',
|
|
34
|
+
'TLS_CHACHA20_POLY1305_SHA256'
|
|
35
|
+
].join(':'),
|
|
36
|
+
sigalgs: 'ecdsa_secp256r1_sha256:rsa_pss_rsae_sha256:rsa_pkcs1_sha256:ecdsa_secp384r1_sha384:rsa_pss_rsae_sha384:rsa_pkcs1_sha384:rsa_pss_rsae_sha512:rsa_pkcs1_sha512',
|
|
37
|
+
rejectUnauthorized: true,
|
|
38
|
+
},
|
|
27
39
|
})
|
|
28
40
|
: new mqtts_1.TlsTransport({
|
|
29
41
|
host: options.url,
|
|
30
42
|
port: 443,
|
|
31
|
-
additionalOptions:
|
|
43
|
+
additionalOptions: {
|
|
44
|
+
...options.additionalOptions,
|
|
45
|
+
// Anti-Bot: Enforce TLS 1.3 with Unified Cipher Suites (iOS & Android 14/15)
|
|
46
|
+
minVersion: 'TLSv1.3',
|
|
47
|
+
maxVersion: 'TLSv1.3',
|
|
48
|
+
ciphers: [
|
|
49
|
+
'TLS_AES_128_GCM_SHA256',
|
|
50
|
+
'TLS_AES_256_GCM_SHA384',
|
|
51
|
+
'TLS_CHACHA20_POLY1305_SHA256'
|
|
52
|
+
].join(':'),
|
|
53
|
+
sigalgs: 'ecdsa_secp256r1_sha256:rsa_pss_rsae_sha256:rsa_pkcs1_sha256:ecdsa_secp384r1_sha384:rsa_pss_rsae_sha384:rsa_pkcs1_sha384:rsa_pss_rsae_sha512:rsa_pkcs1_sha512',
|
|
54
|
+
rejectUnauthorized: true,
|
|
55
|
+
},
|
|
32
56
|
}),
|
|
33
57
|
});
|
|
34
58
|
this.mqttotDebug = (msg, ...args) => (0, shared_1.debugChannel)('mqttot')(`${options.url}: ${msg}`, ...args);
|
|
@@ -65,12 +89,17 @@ class MQTToTClient extends mqtts_1.MqttClient {
|
|
|
65
89
|
* @param {MqttMessage} message
|
|
66
90
|
* @returns {Promise<MqttMessageOutgoing>}
|
|
67
91
|
*/
|
|
68
|
-
|
|
92
|
+
mqttotPublish(message) {
|
|
69
93
|
this.mqttotDebug(`Publishing ${message.payload.byteLength}bytes to topic ${message.topic}`);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
94
|
+
// Anti-bot: Jitter publishing timestamp slightly to mimic organic network latency
|
|
95
|
+
return new Promise(resolve => {
|
|
96
|
+
setTimeout(async () => {
|
|
97
|
+
resolve(await this.publish({
|
|
98
|
+
topic: message.topic,
|
|
99
|
+
payload: await (0, shared_1.compressDeflate)(message.payload),
|
|
100
|
+
qosLevel: message.qosLevel,
|
|
101
|
+
}));
|
|
102
|
+
}, Math.random() * 50 + 10); // 10-60ms random jitter
|
|
74
103
|
});
|
|
75
104
|
}
|
|
76
105
|
/**
|
|
@@ -100,7 +129,7 @@ function mqttotConnectFlow(payload, requirePayload) {
|
|
|
100
129
|
type: mqtts_1.PacketType.Connect,
|
|
101
130
|
options: {
|
|
102
131
|
payload,
|
|
103
|
-
keepAlive:
|
|
132
|
+
keepAlive: 900, // Increased to 15 minutes (900s) to match mobile app behavior for battery saving
|
|
104
133
|
},
|
|
105
134
|
}),
|
|
106
135
|
accept: mqtts_1.isConnAck,
|
|
@@ -47,10 +47,14 @@ MQTToTConnection.thriftConfig = [
|
|
|
47
47
|
thrift_1.ThriftDescriptors.int64('anotherUnknown', 26),
|
|
48
48
|
]),
|
|
49
49
|
thrift_1.ThriftDescriptors.binary('password', 5),
|
|
50
|
-
// polyfill
|
|
50
|
+
// polyfill - Anti-Bot Hardening (Order matters for mobile mimicry)
|
|
51
51
|
thrift_1.ThriftDescriptors.int16('unknown', 5),
|
|
52
52
|
thrift_1.ThriftDescriptors.listOfBinary('getDiffsRequests', 6),
|
|
53
53
|
thrift_1.ThriftDescriptors.binary('zeroRatingTokenHash', 9),
|
|
54
54
|
thrift_1.ThriftDescriptors.mapBinaryBinary('appSpecificInfo', 10),
|
|
55
|
+
// Field 11 is "chat_on" state for presence - often missing in bots
|
|
56
|
+
thrift_1.ThriftDescriptors.boolean('chatOn', 11),
|
|
57
|
+
// Field 12 is "fg_keepalive" - foreground keepalive flag
|
|
58
|
+
thrift_1.ThriftDescriptors.boolean('fgKeepAlive', 12),
|
|
55
59
|
];
|
|
56
60
|
//# sourceMappingURL=mqttot.connection.js.map
|
|
@@ -17,9 +17,6 @@ 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
|
-
const fs = require('fs');
|
|
21
|
-
const path = require('path');
|
|
22
|
-
|
|
23
20
|
class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
24
21
|
get mqtt() {
|
|
25
22
|
return this._mqtt;
|
|
@@ -38,20 +35,7 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
38
35
|
this.emitWarning = (e) => this.emit('warning', e);
|
|
39
36
|
this.ig = ig;
|
|
40
37
|
this.threads = new Map();
|
|
41
|
-
|
|
42
|
-
// === New: observability & metrics ===
|
|
43
|
-
this.metrics = {
|
|
44
|
-
reconnectCount: 0,
|
|
45
|
-
messagesReceived: 0,
|
|
46
|
-
parseErrors: 0,
|
|
47
|
-
lastConnectTime: null,
|
|
48
|
-
backoffAttempts: 0,
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
// Session state persistence file (changeable)
|
|
52
|
-
this._sessionStateFile = path.join('.', '.mqtt_session_state.json');
|
|
53
|
-
this._sessionState = this._loadSessionState();
|
|
54
|
-
|
|
38
|
+
|
|
55
39
|
this.irisHandshake = new iris_handshake_1.IrisHandshake(this);
|
|
56
40
|
this.skywalkerProtocol = new skywalker_protocol_1.SkywalkerProtocol(this);
|
|
57
41
|
this.presenceManager = new presence_manager_1.PresenceManager(this);
|
|
@@ -59,52 +43,21 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
59
43
|
this.errorHandler = new error_handler_1.ErrorHandler(this);
|
|
60
44
|
this.gapHandler = new gap_handler_1.GapHandler(this);
|
|
61
45
|
this.directCommands = new enhanced_direct_commands_1.EnhancedDirectCommands(this);
|
|
62
|
-
|
|
46
|
+
|
|
63
47
|
this.realtimeDebug(`Applying mixins: ${mixins.map(m => m.name).join(', ')}`);
|
|
64
48
|
(0, mixins_1.applyMixins)(mixins, this, this.ig);
|
|
65
|
-
|
|
66
|
-
// Periodic metrics logging (only if not disabled)
|
|
67
|
-
this._metricsInterval = setInterval(() => {
|
|
68
|
-
try {
|
|
69
|
-
if (this.metrics) {
|
|
70
|
-
this.realtimeDebug('METRICS', JSON.stringify(this.metrics));
|
|
71
|
-
}
|
|
72
|
-
} catch (e) { }
|
|
73
|
-
}, 60 * 1000); // every 60s
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// -------------------- Session state persistence --------------------
|
|
77
|
-
_loadSessionState() {
|
|
78
|
-
try {
|
|
79
|
-
if (fs.existsSync(this._sessionStateFile)) {
|
|
80
|
-
const raw = fs.readFileSync(this._sessionStateFile, 'utf8');
|
|
81
|
-
return JSON.parse(raw || '{}');
|
|
82
|
-
}
|
|
83
|
-
} catch (e) {
|
|
84
|
-
// ignore
|
|
85
|
-
}
|
|
86
|
-
return {};
|
|
87
|
-
}
|
|
88
|
-
_saveSessionState() {
|
|
89
|
-
try {
|
|
90
|
-
fs.writeFileSync(this._sessionStateFile, JSON.stringify(this._sessionState || {}, null, 2), 'utf8');
|
|
91
|
-
} catch (e) {
|
|
92
|
-
this.realtimeDebug('Failed to save session state:', e?.message || e);
|
|
93
|
-
}
|
|
94
49
|
}
|
|
95
50
|
|
|
96
|
-
// ------------------------------------------------------------------
|
|
97
|
-
|
|
98
51
|
/**
|
|
99
52
|
* Start Real-Time Listener with Auto-Inbox Fetch + MQTT
|
|
100
53
|
*/
|
|
101
54
|
async startRealTimeListener(options = {}) {
|
|
102
55
|
try {
|
|
103
56
|
console.log('[REALTIME] Starting Real-Time Listener...');
|
|
104
|
-
|
|
57
|
+
|
|
105
58
|
console.log('[REALTIME] Fetching inbox (IRIS data)...');
|
|
106
59
|
const inboxData = await this.ig.direct.getInbox();
|
|
107
|
-
|
|
60
|
+
|
|
108
61
|
console.log('[REALTIME] Connecting to MQTT with IRIS subscription...');
|
|
109
62
|
await this.connect({
|
|
110
63
|
graphQlSubs: [
|
|
@@ -117,15 +70,15 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
117
70
|
],
|
|
118
71
|
irisData: inboxData
|
|
119
72
|
});
|
|
120
|
-
|
|
73
|
+
|
|
121
74
|
console.log('[REALTIME] MQTT Connected with IRIS');
|
|
122
75
|
console.log('----------------------------------------');
|
|
123
76
|
console.log('[REALTIME] Real-Time Listener ACTIVE');
|
|
124
77
|
console.log('[REALTIME] Waiting for messages...');
|
|
125
78
|
console.log('----------------------------------------');
|
|
126
|
-
|
|
79
|
+
|
|
127
80
|
this._setupMessageHandlers();
|
|
128
|
-
|
|
81
|
+
|
|
129
82
|
return { success: true };
|
|
130
83
|
} catch (error) {
|
|
131
84
|
console.error('[REALTIME] Failed:', error.message);
|
|
@@ -137,92 +90,46 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
137
90
|
* Setup automatic message handlers
|
|
138
91
|
*/
|
|
139
92
|
_setupMessageHandlers() {
|
|
140
|
-
// handle raw mqtt 'message' events (parsed by this._parseMessage)
|
|
141
93
|
this.on('message', (data) => {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
this.emit('message_raw', parsed._rawMessage);
|
|
146
|
-
}
|
|
147
|
-
// normalize and emit only if it's a real message
|
|
148
|
-
const normalized = this._normalizeToYowsupLike(parsed);
|
|
149
|
-
if (normalized) {
|
|
150
|
-
this.metrics.messagesReceived++;
|
|
151
|
-
// emit cleaned payload as the primary "live" message (Yowsup-like)
|
|
152
|
-
this.emit('message_live', normalized);
|
|
153
|
-
} else {
|
|
154
|
-
// not a real message -> no message_live; keep raw available
|
|
94
|
+
const msg = this._parseMessage(data);
|
|
95
|
+
if (msg) {
|
|
96
|
+
this.emit('message_live', msg);
|
|
155
97
|
}
|
|
156
98
|
});
|
|
157
99
|
|
|
158
|
-
// handle iris events (graph/iris)
|
|
159
100
|
this.on('iris', (data) => {
|
|
160
|
-
const
|
|
161
|
-
if (
|
|
162
|
-
this.emit('
|
|
163
|
-
}
|
|
164
|
-
const normalized = this._normalizeToYowsupLike(parsed);
|
|
165
|
-
if (normalized) {
|
|
166
|
-
this.metrics.messagesReceived++;
|
|
167
|
-
this.emit('message_live', normalized);
|
|
168
|
-
} else {
|
|
169
|
-
// not a message event
|
|
101
|
+
const msg = this._parseIrisMessage(data);
|
|
102
|
+
if (msg) {
|
|
103
|
+
this.emit('message_live', msg);
|
|
170
104
|
}
|
|
171
105
|
});
|
|
172
106
|
}
|
|
173
107
|
|
|
174
108
|
/**
|
|
175
109
|
* Parse direct message
|
|
176
|
-
* Returns an object that always contains the original raw as _rawMessage when available
|
|
177
110
|
*/
|
|
178
111
|
_parseMessage(data) {
|
|
179
112
|
try {
|
|
180
113
|
const msg = data.message;
|
|
181
114
|
if (!msg) return null;
|
|
182
|
-
|
|
115
|
+
|
|
183
116
|
if (data.parsed) {
|
|
184
|
-
|
|
185
|
-
const base = Object.assign({}, data.parsed);
|
|
186
|
-
base.mqttString = `Mqtt client: [${base.username || (base.userId ? 'user_' + base.userId : 'unknown')}] ${base.text || ''}`;
|
|
187
|
-
base.mqtt = {
|
|
188
|
-
from: base.username || base.userId,
|
|
189
|
-
body: base.text || '',
|
|
190
|
-
timestamp: base.timestamp || Date.now()
|
|
191
|
-
};
|
|
192
|
-
base.status = 'received'; // changed from 'good'
|
|
193
|
-
base._rawMessage = msg;
|
|
194
|
-
return base;
|
|
117
|
+
return data.parsed;
|
|
195
118
|
}
|
|
196
|
-
|
|
119
|
+
|
|
197
120
|
const threadInfo = this.threads.get(msg.thread_id);
|
|
198
|
-
|
|
199
|
-
const text = msg.text || msg.body || '';
|
|
200
|
-
|
|
201
|
-
// Provide mqtt-like representations
|
|
202
|
-
const mqttString = `Mqtt client: [${username}] ${text}`;
|
|
203
|
-
const mqtt = {
|
|
204
|
-
from: username,
|
|
205
|
-
body: text,
|
|
206
|
-
timestamp: msg.timestamp || Date.now()
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
const out = {
|
|
121
|
+
return {
|
|
210
122
|
id: msg.item_id || msg.id,
|
|
211
123
|
userId: msg.user_id || msg.from_user_id,
|
|
212
|
-
username
|
|
213
|
-
text,
|
|
124
|
+
username: msg.username || msg.from_username || `user_${msg.user_id || 'unknown'}`,
|
|
125
|
+
text: msg.text || msg.body || '',
|
|
214
126
|
itemType: msg.item_type || 'text',
|
|
215
127
|
thread: threadInfo?.title || `Thread ${msg.thread_id}`,
|
|
216
128
|
thread_id: msg.thread_id,
|
|
217
129
|
timestamp: msg.timestamp,
|
|
218
130
|
isGroup: threadInfo?.isGroup,
|
|
219
|
-
status: '
|
|
220
|
-
mqttString,
|
|
221
|
-
mqtt,
|
|
222
|
-
_rawMessage: msg
|
|
131
|
+
status: 'good'
|
|
223
132
|
};
|
|
224
|
-
|
|
225
|
-
return out;
|
|
226
133
|
} catch (e) {
|
|
227
134
|
return null;
|
|
228
135
|
}
|
|
@@ -234,125 +141,22 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
234
141
|
_parseIrisMessage(data) {
|
|
235
142
|
try {
|
|
236
143
|
if (data.event !== 'message_create' && !data.type?.includes('message')) {
|
|
237
|
-
|
|
238
|
-
return { _rawIris: data };
|
|
144
|
+
return null;
|
|
239
145
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const text = data.text || '';
|
|
243
|
-
|
|
244
|
-
const mqttString = `Mqtt client: [${username}] ${text}`;
|
|
245
|
-
const mqtt = {
|
|
246
|
-
from: username,
|
|
247
|
-
body: text,
|
|
248
|
-
timestamp: data.timestamp || Date.now()
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
const out = {
|
|
146
|
+
|
|
147
|
+
return {
|
|
252
148
|
id: data.item_id || data.id,
|
|
253
149
|
userId: data.user_id || data.from_user_id,
|
|
254
|
-
username
|
|
255
|
-
text,
|
|
150
|
+
username: data.username || data.from_username || `user_${data.user_id || 'unknown'}`,
|
|
151
|
+
text: data.text || '',
|
|
256
152
|
itemType: data.item_type || 'text',
|
|
257
153
|
thread_id: data.thread_id,
|
|
258
154
|
timestamp: data.timestamp,
|
|
259
|
-
status: '
|
|
260
|
-
mqttString,
|
|
261
|
-
mqtt,
|
|
262
|
-
_rawIris: data
|
|
155
|
+
status: 'good'
|
|
263
156
|
};
|
|
264
|
-
|
|
265
|
-
return out;
|
|
266
157
|
} catch (e) {
|
|
267
|
-
return { _rawIris: data };
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* NORMALIZE to Yowsup-like full payload (but branded "Mqtt client")
|
|
273
|
-
* Returns normalized object or null if the parsed input is NOT a real message
|
|
274
|
-
*
|
|
275
|
-
* Normalized schema (Yowsup-like full payload):
|
|
276
|
-
* {
|
|
277
|
-
* client: 'Mqtt client',
|
|
278
|
-
* from: 'username',
|
|
279
|
-
* fromId: 'numeric id',
|
|
280
|
-
* body: 'text or description',
|
|
281
|
-
* timestamp: 1700000000000,
|
|
282
|
-
* type: 'text'|'image'|'video'|'voice'|'sticker'|'unknown',
|
|
283
|
-
* thread_id: '...',
|
|
284
|
-
* id: 'message id',
|
|
285
|
-
* raw: { original raw object }
|
|
286
|
-
* }
|
|
287
|
-
*/
|
|
288
|
-
_normalizeToYowsupLike(parsed) {
|
|
289
|
-
if (!parsed) return null;
|
|
290
|
-
|
|
291
|
-
// get raw object for detection
|
|
292
|
-
const raw = parsed._rawMessage || parsed._rawIris || parsed;
|
|
293
|
-
|
|
294
|
-
// Heuristics to decide if this is a "real message" (text/media/voice)
|
|
295
|
-
// Accept if:
|
|
296
|
-
// - parsed.text or parsed.body present and non-empty
|
|
297
|
-
// - or raw contains visual_media/media fields
|
|
298
|
-
// - or itemType indicates media
|
|
299
|
-
const hasText = Boolean(parsed.text && String(parsed.text).trim().length > 0);
|
|
300
|
-
const hasBodyField = Boolean(parsed.body && String(parsed.body).trim().length > 0);
|
|
301
|
-
const hasMediaField = Boolean(
|
|
302
|
-
(raw && (raw.visual_media || raw.media || raw.media_share || raw.items || raw.has_media)) ||
|
|
303
|
-
(parsed.itemType && String(parsed.itemType).toLowerCase() !== 'text')
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
if (!hasText && !hasBodyField && !hasMediaField) {
|
|
307
|
-
// Not a message that Yowsup would surface — drop from message_live
|
|
308
158
|
return null;
|
|
309
159
|
}
|
|
310
|
-
|
|
311
|
-
// Determine type
|
|
312
|
-
let type = 'text';
|
|
313
|
-
try {
|
|
314
|
-
if (hasMediaField) {
|
|
315
|
-
// try to deduce image/video/voice from raw fields if possible
|
|
316
|
-
const rl = raw || {};
|
|
317
|
-
if (rl.item_type && String(rl.item_type).toLowerCase().includes('video')) type = 'video';
|
|
318
|
-
else if (rl.item_type && String(rl.item_type).toLowerCase().includes('voice')) type = 'voice';
|
|
319
|
-
else if (rl.media && Array.isArray(rl.media)) {
|
|
320
|
-
// naive check for media types in array
|
|
321
|
-
const m0 = rl.media[0] || {};
|
|
322
|
-
if (m0.media_type === 2 || String(m0.media_type) === '2') type = 'video';
|
|
323
|
-
else type = 'image';
|
|
324
|
-
} else if (rl.visual_media) {
|
|
325
|
-
type = 'image';
|
|
326
|
-
} else {
|
|
327
|
-
type = 'unknown';
|
|
328
|
-
}
|
|
329
|
-
} else {
|
|
330
|
-
type = 'text';
|
|
331
|
-
}
|
|
332
|
-
} catch (e) {
|
|
333
|
-
type = 'unknown';
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// body selection
|
|
337
|
-
const body = hasText ? String(parsed.text).trim() : (hasBodyField ? String(parsed.body).trim() : (type !== 'text' ? `[${type.toUpperCase()}]` : ''));
|
|
338
|
-
|
|
339
|
-
// timestamp fallback
|
|
340
|
-
const ts = parsed.timestamp ? Number(parsed.timestamp) : (Date.now());
|
|
341
|
-
|
|
342
|
-
// Build normalized object (Yowsup-like full payload)
|
|
343
|
-
const normalized = {
|
|
344
|
-
client: 'Mqtt client',
|
|
345
|
-
from: parsed.username || parsed.from || (parsed.userId ? `user_${parsed.userId}` : 'unknown'),
|
|
346
|
-
fromId: String(parsed.userId || (raw && (raw.from_user_id || raw.user_id)) || 'unknown'),
|
|
347
|
-
body: body,
|
|
348
|
-
timestamp: ts,
|
|
349
|
-
type,
|
|
350
|
-
thread_id: parsed.thread_id || parsed.thread || (raw && (raw.thread_id || raw.thread)) || null,
|
|
351
|
-
id: parsed.id || (raw && (raw.item_id || raw.id)) || null,
|
|
352
|
-
raw: raw
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
return normalized;
|
|
356
160
|
}
|
|
357
161
|
|
|
358
162
|
setInitOptions(initOptions) {
|
|
@@ -426,36 +230,37 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
426
230
|
this.realtimeDebug(`SessionID generated (fallback): ${sessionid}`);
|
|
427
231
|
}
|
|
428
232
|
const password = `sessionid=${sessionid}`;
|
|
429
|
-
|
|
430
|
-
//
|
|
431
|
-
const persistedClientMqttSessionId = this._sessionState?.clientMqttSessionId ? BigInt(this._sessionState.clientMqttSessionId) : null;
|
|
432
|
-
const clientMqttSessionIdValue = persistedClientMqttSessionId ?? (BigInt(Date.now()) & BigInt(0xffffffff));
|
|
433
|
-
|
|
233
|
+
|
|
234
|
+
// MQTT Client Info - Unified Mobile Profile (iOS & Android)
|
|
434
235
|
this.connection = new mqttot_1.MQTToTConnection({
|
|
435
236
|
clientIdentifier: deviceId.substring(0, 20),
|
|
436
237
|
clientInfo: {
|
|
437
238
|
userId: BigInt(Number(this.ig.state.cookieUserId)),
|
|
438
239
|
userAgent,
|
|
439
|
-
clientCapabilities: 183,
|
|
240
|
+
clientCapabilities: this.ig.state.platform === 'ios' ? 195 : 183, // Platform-specific capabilities
|
|
440
241
|
endpointCapabilities: 0,
|
|
441
242
|
publishFormat: 1,
|
|
442
243
|
noAutomaticForeground: false,
|
|
443
244
|
makeUserAvailableInForeground: true,
|
|
444
245
|
deviceId,
|
|
445
246
|
isInitiallyForeground: true,
|
|
446
|
-
networkType: 1,
|
|
247
|
+
networkType: 1, // WIFI
|
|
447
248
|
networkSubtype: 0,
|
|
448
|
-
clientMqttSessionId:
|
|
449
|
-
subscribeTopics:
|
|
249
|
+
clientMqttSessionId: BigInt(Date.now()) & BigInt(0xffffffff),
|
|
250
|
+
subscribeTopics: this.ig.state.platform === 'ios'
|
|
251
|
+
? [88, 135, 149, 150, 133, 146, 230, 231]
|
|
252
|
+
: [88, 135, 149, 150, 133, 146], // Android usually has fewer default subscriptions
|
|
450
253
|
clientType: 'cookie_auth',
|
|
451
|
-
appId:
|
|
254
|
+
appId: this.ig.state.platform === 'ios'
|
|
255
|
+
? BigInt(1217981644879628)
|
|
256
|
+
: BigInt(1217981644879628), // Same for both on mobile
|
|
452
257
|
deviceSecret: '',
|
|
453
258
|
clientStack: 3,
|
|
454
259
|
...(this.initOptions?.connectOverrides || {}),
|
|
455
260
|
},
|
|
456
261
|
password,
|
|
457
262
|
appSpecificInfo: {
|
|
458
|
-
app_version: this.ig.state.appVersion,
|
|
263
|
+
app_version: this.ig.state.platform === 'ios' ? '347.0.0.36.89' : this.ig.state.appVersion,
|
|
459
264
|
'X-IG-Capabilities': this.ig.state.capabilitiesHeader,
|
|
460
265
|
everclear_subscriptions: JSON.stringify({
|
|
461
266
|
inapp_notification_subscribe_comment: '17899377895239777',
|
|
@@ -464,126 +269,53 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
464
269
|
presence_subscribe: '17846944882223835',
|
|
465
270
|
}),
|
|
466
271
|
'User-Agent': userAgent,
|
|
467
|
-
'Accept-Language': this.ig.state.language
|
|
272
|
+
'Accept-Language': this.ig.state.language,
|
|
468
273
|
},
|
|
469
274
|
});
|
|
470
|
-
|
|
471
|
-
// update persisted state with chosen session id
|
|
472
|
-
try {
|
|
473
|
-
this._sessionState = this._sessionState || {};
|
|
474
|
-
this._sessionState.clientMqttSessionId = String(clientMqttSessionIdValue);
|
|
475
|
-
this._saveSessionState();
|
|
476
|
-
} catch (e) {
|
|
477
|
-
// ignore
|
|
478
|
-
}
|
|
479
275
|
}
|
|
480
276
|
async connect(options) {
|
|
481
277
|
this.setInitOptions(options);
|
|
482
278
|
this.constructConnection();
|
|
483
279
|
const { MQTToTClient } = require("../mqttot");
|
|
484
280
|
const { compressDeflate } = require("../shared");
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
return await compressDeflate(this.connection.toThrift());
|
|
498
|
-
},
|
|
499
|
-
autoReconnect: true,
|
|
500
|
-
requirePayload: true,
|
|
501
|
-
// pass through any socks/proxy options if provided
|
|
502
|
-
socksOptions: this.initOptions?.socksOptions,
|
|
503
|
-
additionalOptions: this.initOptions?.additionalOptions
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
await this._mqtt.connect();
|
|
507
|
-
|
|
508
|
-
// mark metrics
|
|
509
|
-
this.metrics.reconnectCount++;
|
|
510
|
-
this.metrics.lastConnectTime = Date.now();
|
|
511
|
-
this.metrics.backoffAttempts += (attempt - 1);
|
|
512
|
-
|
|
513
|
-
// If we reach here, connected successfully
|
|
514
|
-
break;
|
|
515
|
-
} catch (err) {
|
|
516
|
-
lastError = err;
|
|
517
|
-
this.realtimeDebug(`MQTT connect attempt ${attempt} failed: ${err?.message || err}`);
|
|
518
|
-
|
|
519
|
-
// exponential backoff with jitter
|
|
520
|
-
const base = 1000; // 1s
|
|
521
|
-
const delayMs = Math.min(30000, base * Math.pow(2, attempt - 1)) + Math.floor(Math.random() * 250);
|
|
522
|
-
this.realtimeDebug(`Waiting ${delayMs}ms before next connect attempt...`);
|
|
523
|
-
await (0, shared_1.delay)(delayMs);
|
|
524
|
-
|
|
525
|
-
// cooldown behavior: if too many attempts, wait longer
|
|
526
|
-
if (attempt >= maxAttempts) {
|
|
527
|
-
this.realtimeDebug('Max connect attempts reached, giving up for now.');
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
if (!this._mqtt) {
|
|
532
|
-
// fail with the last error
|
|
533
|
-
throw lastError || new Error('Failed to create MQTT client');
|
|
534
|
-
}
|
|
535
|
-
// ----------------------------------------------------------------------------
|
|
536
|
-
|
|
281
|
+
|
|
282
|
+
this._mqtt = new MQTToTClient({
|
|
283
|
+
url: 'edge-mqtt.facebook.com',
|
|
284
|
+
payloadProvider: async () => {
|
|
285
|
+
return await compressDeflate(this.connection.toThrift());
|
|
286
|
+
},
|
|
287
|
+
autoReconnect: true,
|
|
288
|
+
requirePayload: true,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
await this._mqtt.connect();
|
|
292
|
+
|
|
537
293
|
this.commands = new commands_1.Commands(this._mqtt);
|
|
538
|
-
|
|
294
|
+
|
|
539
295
|
this.emit('connected');
|
|
540
|
-
|
|
541
|
-
// Enhanced message handling: try tolerant parse, log unknown fields, fallback raw
|
|
296
|
+
|
|
542
297
|
this._mqtt.on('message', async (msg) => {
|
|
543
298
|
const topicMap = this.mqtt?.topicMap;
|
|
544
299
|
const topic = topicMap?.get(msg.topic);
|
|
545
|
-
|
|
300
|
+
|
|
546
301
|
if (topic && topic.parser && !topic.noParse) {
|
|
547
302
|
try {
|
|
548
303
|
const unzipped = await (0, shared_1.tryUnzipAsync)(msg.payload);
|
|
549
|
-
|
|
550
|
-
try {
|
|
551
|
-
parsedMessages = topic.parser.parseMessage(topic, unzipped);
|
|
552
|
-
} catch (parseErr) {
|
|
553
|
-
// Tolerant parsing: log and attempt safe fallback
|
|
554
|
-
this.metrics.parseErrors++;
|
|
555
|
-
this.realtimeDebug('Parse error (topic parser) - attempting tolerant fallback:', parseErr?.message || parseErr);
|
|
556
|
-
|
|
557
|
-
// Attempt to decode as JSON if possible (some thrift->json conversions may be present in tooling)
|
|
558
|
-
try {
|
|
559
|
-
const s = unzipped.toString('utf8');
|
|
560
|
-
parsedMessages = JSON.parse(s);
|
|
561
|
-
this.realtimeDebug('Fallback parsed as JSON.');
|
|
562
|
-
} catch (jsonErr) {
|
|
563
|
-
// Last resort: emit raw payload (preserve for debugging)
|
|
564
|
-
this.realtimeDebug('Fallback JSON parse failed; emitting raw payload.');
|
|
565
|
-
this.emit('receiveRaw', { topic: msg.topic, payload: unzipped, original: msg });
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
304
|
+
const parsedMessages = topic.parser.parseMessage(topic, unzipped);
|
|
570
305
|
this.emit('receive', topic, Array.isArray(parsedMessages) ? parsedMessages : [parsedMessages]);
|
|
571
|
-
} catch
|
|
572
|
-
// Silent parse error
|
|
573
|
-
this.metrics.parseErrors++;
|
|
574
|
-
this.realtimeDebug('Silent parse/unzip error in message handler:', e?.message || e);
|
|
306
|
+
} catch(e) {
|
|
307
|
+
// Silent parse error
|
|
575
308
|
}
|
|
576
309
|
} else {
|
|
577
310
|
try {
|
|
578
311
|
await (0, shared_1.tryUnzipAsync)(msg.payload);
|
|
579
312
|
this.emit('receiveRaw', msg);
|
|
580
|
-
} catch
|
|
313
|
+
} catch(e) {
|
|
581
314
|
// Silent decompress error
|
|
582
315
|
}
|
|
583
316
|
}
|
|
584
317
|
});
|
|
585
318
|
this._mqtt.on('error', this.emitError);
|
|
586
|
-
|
|
587
319
|
await (0, shared_1.delay)(100);
|
|
588
320
|
if (this.initOptions.graphQlSubs && this.initOptions.graphQlSubs.length > 0) {
|
|
589
321
|
await this.graphQlSubscribe(this.initOptions.graphQlSubs);
|
|
@@ -614,23 +346,12 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
614
346
|
url: '/api/v1/direct_v2/threads/get_most_recent_message/',
|
|
615
347
|
method: 'POST',
|
|
616
348
|
});
|
|
617
|
-
} catch
|
|
349
|
+
} catch(e) {
|
|
618
350
|
// Silent force fetch error
|
|
619
351
|
}
|
|
620
352
|
} catch (error) {
|
|
621
353
|
// Silent inbox fetch error - MQTT still listening
|
|
622
354
|
}
|
|
623
|
-
|
|
624
|
-
// Persist last used clientMqttSessionId and optionally last seq_id if present in initOptions
|
|
625
|
-
try {
|
|
626
|
-
const clientMqttSessionId = this.connection?.toThrift()?.clientInfo?.clientMqttSessionId || this._sessionState?.clientMqttSessionId;
|
|
627
|
-
if (clientMqttSessionId) {
|
|
628
|
-
this._sessionState = this._sessionState || {};
|
|
629
|
-
this._sessionState.clientMqttSessionId = String(clientMqttSessionId);
|
|
630
|
-
this._saveSessionState();
|
|
631
|
-
}
|
|
632
|
-
} catch (e) { /* ignore */ }
|
|
633
|
-
|
|
634
355
|
this._setupMessageHandlers();
|
|
635
356
|
}
|
|
636
357
|
/**
|
|
@@ -646,7 +367,7 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
646
367
|
console.log('[RealtimeClient] Connecting from saved session...');
|
|
647
368
|
|
|
648
369
|
const savedOptions = authStateHelper.getMqttConnectOptions?.();
|
|
649
|
-
|
|
370
|
+
|
|
650
371
|
const connectOptions = {
|
|
651
372
|
graphQlSubs: options.graphQlSubs || savedOptions?.graphQlSubs || ['ig_sub_direct', 'ig_sub_direct_v2_message_sync'],
|
|
652
373
|
skywalkerSubs: options.skywalkerSubs || savedOptions?.skywalkerSubs || ['presence_subscribe', 'typing_subscribe'],
|
|
@@ -660,12 +381,8 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
660
381
|
hasIrisData: !!connectOptions.irisData
|
|
661
382
|
});
|
|
662
383
|
|
|
663
|
-
// merge any persisted mqtt connect options into initOptions for continuity
|
|
664
|
-
this.initOptions = { ...(this.initOptions || {}), ...(savedOptions || {}) };
|
|
665
|
-
|
|
666
384
|
await this.connect(connectOptions);
|
|
667
385
|
|
|
668
|
-
// Save session state into auth helper if supported
|
|
669
386
|
if (authStateHelper.saveMqttSession) {
|
|
670
387
|
try {
|
|
671
388
|
await authStateHelper.saveMqttSession(this);
|
|
@@ -675,11 +392,6 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
675
392
|
}
|
|
676
393
|
}
|
|
677
394
|
|
|
678
|
-
// Also persist locally
|
|
679
|
-
try {
|
|
680
|
-
if (this._sessionState) this._saveSessionState();
|
|
681
|
-
} catch (e) { }
|
|
682
|
-
|
|
683
395
|
return this;
|
|
684
396
|
}
|
|
685
397
|
/**
|
|
@@ -689,21 +401,13 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
689
401
|
async saveSession(authStateHelper) {
|
|
690
402
|
if (!authStateHelper || !authStateHelper.saveMqttSession) {
|
|
691
403
|
console.warn('[RealtimeClient] No authStateHelper provided');
|
|
692
|
-
// still save local state
|
|
693
|
-
try { this._saveSessionState(); } catch (e) { }
|
|
694
404
|
return false;
|
|
695
405
|
}
|
|
696
406
|
await authStateHelper.saveMqttSession(this);
|
|
697
|
-
// also persist locally
|
|
698
|
-
try { this._saveSessionState(); } catch (e) { }
|
|
699
407
|
return true;
|
|
700
408
|
}
|
|
701
409
|
disconnect() {
|
|
702
410
|
this.safeDisconnect = true;
|
|
703
|
-
// clear metrics interval when shutting down gracefully
|
|
704
|
-
try {
|
|
705
|
-
if (this._metricsInterval) clearInterval(this._metricsInterval);
|
|
706
|
-
} catch (e) { }
|
|
707
411
|
return this.mqtt?.disconnect() ?? Promise.resolve();
|
|
708
412
|
}
|
|
709
413
|
graphQlSubscribe(sub) {
|
|
@@ -737,13 +441,6 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
737
441
|
throw new mqtts_1.IllegalStateError('connect() must be called before irisSubscribe()');
|
|
738
442
|
}
|
|
739
443
|
this.realtimeDebug(`Iris Sub to: seqId: ${seq_id}, snapshot: ${snapshot_at_ms}`);
|
|
740
|
-
// persist last seq_id for continuity
|
|
741
|
-
try {
|
|
742
|
-
this._sessionState = this._sessionState || {};
|
|
743
|
-
if (seq_id) this._sessionState.last_seq_id = seq_id;
|
|
744
|
-
if (snapshot_at_ms) this._sessionState.last_snapshot_at_ms = snapshot_at_ms;
|
|
745
|
-
this._saveSessionState();
|
|
746
|
-
} catch (e) { /* ignore */ }
|
|
747
444
|
return this.commands.updateSubscriptions({
|
|
748
445
|
topic: constants_1.Topics.IRIS_SUB,
|
|
749
446
|
data: {
|
|
@@ -753,68 +450,6 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
753
450
|
},
|
|
754
451
|
});
|
|
755
452
|
}
|
|
756
|
-
|
|
757
|
-
// ---------------- Dynamic subscription helpers ----------------
|
|
758
|
-
async addGraphQlSub(sub) {
|
|
759
|
-
try {
|
|
760
|
-
const current = this.initOptions.graphQlSubs || [];
|
|
761
|
-
if (!current.includes(sub)) {
|
|
762
|
-
current.push(sub);
|
|
763
|
-
this.initOptions.graphQlSubs = current;
|
|
764
|
-
if (this.commands) {
|
|
765
|
-
await this.graphQlSubscribe(current);
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
return true;
|
|
769
|
-
} catch (e) {
|
|
770
|
-
this.realtimeDebug('addGraphQlSub error:', e?.message || e);
|
|
771
|
-
return false;
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
async removeGraphQlSub(sub) {
|
|
775
|
-
try {
|
|
776
|
-
const current = (this.initOptions.graphQlSubs || []).filter(s => s !== sub);
|
|
777
|
-
this.initOptions.graphQlSubs = current;
|
|
778
|
-
if (this.commands) {
|
|
779
|
-
await this.graphQlSubscribe(current);
|
|
780
|
-
}
|
|
781
|
-
return true;
|
|
782
|
-
} catch (e) {
|
|
783
|
-
this.realtimeDebug('removeGraphQlSub error:', e?.message || e);
|
|
784
|
-
return false;
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
async addSkywalkerSub(sub) {
|
|
788
|
-
try {
|
|
789
|
-
const current = this.initOptions.skywalkerSubs || [];
|
|
790
|
-
if (!current.includes(sub)) {
|
|
791
|
-
current.push(sub);
|
|
792
|
-
this.initOptions.skywalkerSubs = current;
|
|
793
|
-
if (this.commands) {
|
|
794
|
-
await this.skywalkerSubscribe(current);
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
return true;
|
|
798
|
-
} catch (e) {
|
|
799
|
-
this.realtimeDebug('addSkywalkerSub error:', e?.message || e);
|
|
800
|
-
return false;
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
async removeSkywalkerSub(sub) {
|
|
804
|
-
try {
|
|
805
|
-
const current = (this.initOptions.skywalkerSubs || []).filter(s => s !== sub);
|
|
806
|
-
this.initOptions.skywalkerSubs = current;
|
|
807
|
-
if (this.commands) {
|
|
808
|
-
await this.skywalkerSubscribe(current);
|
|
809
|
-
}
|
|
810
|
-
return true;
|
|
811
|
-
} catch (e) {
|
|
812
|
-
this.realtimeDebug('removeSkywalkerSub error:', e?.message || e);
|
|
813
|
-
return false;
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
// ----------------------------------------------------------------
|
|
817
|
-
|
|
818
453
|
}
|
|
819
454
|
exports.RealtimeClient = RealtimeClient;
|
|
820
455
|
//# sourceMappingURL=realtime.client.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodejs-insta-private-api-mqtt",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
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": {
|