nodejs-insta-private-api-mqtt 1.0.0
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 +1650 -0
- package/dist/constants/constants.js +280 -0
- package/dist/constants/index.js +41 -0
- package/dist/core/client.js +243 -0
- package/dist/core/repository.js +7 -0
- package/dist/core/request.js +212 -0
- package/dist/core/state.js +1456 -0
- package/dist/core/utils.js +786 -0
- package/dist/downloadMedia.js +381 -0
- package/dist/errors/index.d.ts +16 -0
- package/dist/errors/index.js +30 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/fbns/fbns.client.d.ts +32 -0
- package/dist/fbns/fbns.client.events.d.ts +41 -0
- package/dist/fbns/fbns.client.events.js +3 -0
- package/dist/fbns/fbns.client.events.js.map +1 -0
- package/dist/fbns/fbns.client.js +179 -0
- package/dist/fbns/fbns.client.js.map +1 -0
- package/dist/fbns/fbns.device-auth.d.ts +17 -0
- package/dist/fbns/fbns.device-auth.js +54 -0
- package/dist/fbns/fbns.device-auth.js.map +1 -0
- package/dist/fbns/fbns.types.d.ts +83 -0
- package/dist/fbns/fbns.types.js +3 -0
- package/dist/fbns/fbns.types.js.map +1 -0
- package/dist/fbns/fbns.utilities.d.ts +2 -0
- package/dist/fbns/fbns.utilities.js +79 -0
- package/dist/fbns/fbns.utilities.js.map +1 -0
- package/dist/fbns/index.d.ts +4 -0
- package/dist/fbns/index.js +21 -0
- package/dist/fbns/index.js.map +1 -0
- package/dist/index.js +39 -0
- package/dist/mqttot/index.d.ts +4 -0
- package/dist/mqttot/index.js +21 -0
- package/dist/mqttot/index.js.map +1 -0
- package/dist/mqttot/mqttot.client.d.ts +39 -0
- package/dist/mqttot/mqttot.client.js +120 -0
- package/dist/mqttot/mqttot.client.js.map +1 -0
- package/dist/mqttot/mqttot.connect.request.packet.d.ts +7 -0
- package/dist/mqttot/mqttot.connect.request.packet.js +9 -0
- package/dist/mqttot/mqttot.connect.request.packet.js.map +1 -0
- package/dist/mqttot/mqttot.connect.response.packet.d.ts +7 -0
- package/dist/mqttot/mqttot.connect.response.packet.js +24 -0
- package/dist/mqttot/mqttot.connect.response.packet.js.map +1 -0
- package/dist/mqttot/mqttot.connection.d.ts +57 -0
- package/dist/mqttot/mqttot.connection.js +56 -0
- package/dist/mqttot/mqttot.connection.js.map +1 -0
- package/dist/package.json +59 -0
- package/dist/realtime/commands/commands.d.ts +15 -0
- package/dist/realtime/commands/commands.js +21 -0
- package/dist/realtime/commands/commands.js.map +1 -0
- package/dist/realtime/commands/direct.commands.d.ts +75 -0
- package/dist/realtime/commands/direct.commands.js +186 -0
- package/dist/realtime/commands/direct.commands.js.map +1 -0
- package/dist/realtime/commands/enhanced.direct.commands.js +987 -0
- package/dist/realtime/commands/index.d.ts +2 -0
- package/dist/realtime/commands/index.js +19 -0
- package/dist/realtime/commands/index.js.map +1 -0
- package/dist/realtime/delta-sync.manager.js +293 -0
- package/dist/realtime/features/dm-sender.js +88 -0
- package/dist/realtime/features/error-handler.js +73 -0
- package/dist/realtime/features/gap-handler.js +61 -0
- package/dist/realtime/features/presence.manager.js +66 -0
- package/dist/realtime/index.js +30 -0
- package/dist/realtime/messages/app-presence.event.d.ts +9 -0
- package/dist/realtime/messages/app-presence.event.js +3 -0
- package/dist/realtime/messages/app-presence.event.js.map +1 -0
- package/dist/realtime/messages/index.d.ts +3 -0
- package/dist/realtime/messages/index.js +20 -0
- package/dist/realtime/messages/index.js.map +1 -0
- package/dist/realtime/messages/message-sync.message.d.ts +222 -0
- package/dist/realtime/messages/message-sync.message.js +43 -0
- package/dist/realtime/messages/message-sync.message.js.map +1 -0
- package/dist/realtime/messages/realtime-sub.direct.data.d.ts +11 -0
- package/dist/realtime/messages/realtime-sub.direct.data.js +3 -0
- package/dist/realtime/messages/realtime-sub.direct.data.js.map +1 -0
- package/dist/realtime/messages/thread-update.message.d.ts +68 -0
- package/dist/realtime/messages/thread-update.message.js +3 -0
- package/dist/realtime/messages/thread-update.message.js.map +1 -0
- package/dist/realtime/mixins/index.d.ts +3 -0
- package/dist/realtime/mixins/index.js +20 -0
- package/dist/realtime/mixins/index.js.map +1 -0
- package/dist/realtime/mixins/message-sync.mixin.d.ts +8 -0
- package/dist/realtime/mixins/message-sync.mixin.js +381 -0
- package/dist/realtime/mixins/message-sync.mixin.js.map +1 -0
- package/dist/realtime/mixins/mixin.d.ts +19 -0
- package/dist/realtime/mixins/mixin.js +41 -0
- package/dist/realtime/mixins/mixin.js.map +1 -0
- package/dist/realtime/mixins/presence-typing.mixin.js +33 -0
- package/dist/realtime/mixins/realtime-sub.mixin.d.ts +8 -0
- package/dist/realtime/mixins/realtime-sub.mixin.js +55 -0
- package/dist/realtime/mixins/realtime-sub.mixin.js.map +1 -0
- package/dist/realtime/parsers/graphql-parser.js +43 -0
- package/dist/realtime/parsers/graphql.parser.d.ts +15 -0
- package/dist/realtime/parsers/graphql.parser.js +22 -0
- package/dist/realtime/parsers/graphql.parser.js.map +1 -0
- package/dist/realtime/parsers/index.d.ts +6 -0
- package/dist/realtime/parsers/index.js +23 -0
- package/dist/realtime/parsers/index.js.map +1 -0
- package/dist/realtime/parsers/iris-parser.js +43 -0
- package/dist/realtime/parsers/iris.parser.d.ts +17 -0
- package/dist/realtime/parsers/iris.parser.js +10 -0
- package/dist/realtime/parsers/iris.parser.js.map +1 -0
- package/dist/realtime/parsers/json-parser.js +43 -0
- package/dist/realtime/parsers/json.parser.d.ts +6 -0
- package/dist/realtime/parsers/json.parser.js +10 -0
- package/dist/realtime/parsers/json.parser.js.map +1 -0
- package/dist/realtime/parsers/parser.d.ts +9 -0
- package/dist/realtime/parsers/parser.js +3 -0
- package/dist/realtime/parsers/parser.js.map +1 -0
- package/dist/realtime/parsers/region-hint-parser.js +43 -0
- package/dist/realtime/parsers/region-hint.parser.d.ts +12 -0
- package/dist/realtime/parsers/region-hint.parser.js +15 -0
- package/dist/realtime/parsers/region-hint.parser.js.map +1 -0
- package/dist/realtime/parsers/skywalker-parser.js +43 -0
- package/dist/realtime/parsers/skywalker.parser.d.ts +12 -0
- package/dist/realtime/parsers/skywalker.parser.js +15 -0
- package/dist/realtime/parsers/skywalker.parser.js.map +1 -0
- package/dist/realtime/parsers-advanced.js +158 -0
- package/dist/realtime/proto/common.proto +38 -0
- package/dist/realtime/proto/direct.proto +65 -0
- package/dist/realtime/proto/ig-messages.proto +83 -0
- package/dist/realtime/proto/iris.proto +188 -0
- package/dist/realtime/proto-parser.js +195 -0
- package/dist/realtime/protocols/iris.handshake.js +74 -0
- package/dist/realtime/protocols/proto-definitions.js +80 -0
- package/dist/realtime/protocols/skywalker.protocol.js +91 -0
- package/dist/realtime/realtime.client.events.js +3 -0
- package/dist/realtime/realtime.client.js +449 -0
- package/dist/realtime/realtime.service.js +462 -0
- package/dist/realtime/reconnect.manager.js +94 -0
- package/dist/realtime/session.manager.js +121 -0
- package/dist/realtime/subscriptions/graphql.subscription.d.ts +47 -0
- package/dist/realtime/subscriptions/graphql.subscription.js +99 -0
- package/dist/realtime/subscriptions/graphql.subscription.js.map +1 -0
- package/dist/realtime/subscriptions/index.d.ts +2 -0
- package/dist/realtime/subscriptions/index.js +19 -0
- package/dist/realtime/subscriptions/index.js.map +1 -0
- package/dist/realtime/subscriptions/skywalker.subscription.d.ts +4 -0
- package/dist/realtime/subscriptions/skywalker.subscription.js +13 -0
- package/dist/realtime/subscriptions/skywalker.subscription.js.map +1 -0
- package/dist/realtime/topic-map.js +71 -0
- package/dist/realtime/topic.js +80 -0
- package/dist/repositories/account.repository.js +261 -0
- package/dist/repositories/direct-thread.repository.js +247 -0
- package/dist/repositories/direct.repository.js +153 -0
- package/dist/repositories/feed.repository.js +233 -0
- package/dist/repositories/friendship.repository.js +190 -0
- package/dist/repositories/hashtag.repository.js +101 -0
- package/dist/repositories/highlights.repository.js +127 -0
- package/dist/repositories/location.repository.js +84 -0
- package/dist/repositories/media.repository.js +165 -0
- package/dist/repositories/story.repository.js +156 -0
- package/dist/repositories/upload.repository.js +167 -0
- package/dist/repositories/user.repository.js +94 -0
- package/dist/sendmedia/index.js +11 -0
- package/dist/sendmedia/sendFile.js +154 -0
- package/dist/sendmedia/sendPhoto.js +145 -0
- package/dist/sendmedia/uploadPhoto.js +175 -0
- package/dist/sendmedia/uploadfFile.js +264 -0
- package/dist/services/live.service.js +147 -0
- package/dist/services/search.service.js +116 -0
- package/dist/shared/index.js +35 -0
- package/dist/shared/shared.js +86 -0
- package/dist/thrift/index.d.ts +3 -0
- package/dist/thrift/index.js +20 -0
- package/dist/thrift/index.js.map +1 -0
- package/dist/thrift/thrift.d.ts +59 -0
- package/dist/thrift/thrift.js +101 -0
- package/dist/thrift/thrift.js.map +1 -0
- package/dist/thrift/thrift.reading.d.ts +41 -0
- package/dist/thrift/thrift.reading.js +327 -0
- package/dist/thrift/thrift.reading.js.map +1 -0
- package/dist/thrift/thrift.writing.d.ts +44 -0
- package/dist/thrift/thrift.writing.js +342 -0
- package/dist/thrift/thrift.writing.js.map +1 -0
- package/dist/types/index.js +285 -0
- package/dist/useMultiFileAuthState.js +437 -0
- package/dist/utils/helper-1.js +1 -0
- package/dist/utils/helper-10.js +1 -0
- package/dist/utils/helper-11.js +1 -0
- package/dist/utils/helper-12.js +1 -0
- package/dist/utils/helper-13.js +1 -0
- package/dist/utils/helper-14.js +1 -0
- package/dist/utils/helper-15.js +1 -0
- package/dist/utils/helper-16.js +1 -0
- package/dist/utils/helper-17.js +1 -0
- package/dist/utils/helper-18.js +1 -0
- package/dist/utils/helper-19.js +1 -0
- package/dist/utils/helper-2.js +1 -0
- package/dist/utils/helper-20.js +1 -0
- package/dist/utils/helper-21.js +1 -0
- package/dist/utils/helper-22.js +1 -0
- package/dist/utils/helper-23.js +1 -0
- package/dist/utils/helper-24.js +1 -0
- package/dist/utils/helper-25.js +1 -0
- package/dist/utils/helper-26.js +1 -0
- package/dist/utils/helper-27.js +1 -0
- package/dist/utils/helper-28.js +1 -0
- package/dist/utils/helper-29.js +1 -0
- package/dist/utils/helper-3.js +1 -0
- package/dist/utils/helper-30.js +1 -0
- package/dist/utils/helper-4.js +1 -0
- package/dist/utils/helper-5.js +1 -0
- package/dist/utils/helper-6.js +1 -0
- package/dist/utils/helper-7.js +1 -0
- package/dist/utils/helper-8.js +1 -0
- package/dist/utils/helper-9.js +1 -0
- package/dist/utils/index.js +280 -0
- package/examples/listen-to-messages.js +86 -0
- package/package.json +79 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
const EventEmitter = require('events');
|
|
2
|
+
const mqtt = require('mqtt');
|
|
3
|
+
const { v4: uuidv4 } = require('uuid');
|
|
4
|
+
const { Topics, RealtimeTopicsArray, REALTIME } = require('./topic');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Instagram Realtime MQTT Service
|
|
8
|
+
*
|
|
9
|
+
* Implements Instagram's realtime messaging system using MQTT
|
|
10
|
+
* with the correct endpoint: edge-mqtt.facebook.com
|
|
11
|
+
*
|
|
12
|
+
* @class RealtimeService
|
|
13
|
+
* @extends EventEmitter
|
|
14
|
+
*/
|
|
15
|
+
class RealtimeService extends EventEmitter {
|
|
16
|
+
constructor(client) {
|
|
17
|
+
super();
|
|
18
|
+
|
|
19
|
+
this.client = client;
|
|
20
|
+
this.mqttClient = null;
|
|
21
|
+
this.isConnected = false;
|
|
22
|
+
this.isConnecting = false;
|
|
23
|
+
this.reconnectAttempts = 0;
|
|
24
|
+
this.maxReconnectAttempts = 10;
|
|
25
|
+
this.reconnectDelay = 5000; // 5 seconds
|
|
26
|
+
this.keepalive = 60;
|
|
27
|
+
this.cleanSession = false;
|
|
28
|
+
|
|
29
|
+
// MQTT Configuration
|
|
30
|
+
this.broker = REALTIME.HOST_NAME_V6;
|
|
31
|
+
this.port = 443; // TLS
|
|
32
|
+
this.protocol = 'mqtts';
|
|
33
|
+
this.username = 'fbns';
|
|
34
|
+
|
|
35
|
+
// Client ID generated
|
|
36
|
+
this.clientId = `android-${uuidv4().replace(/-/g, '')}`;
|
|
37
|
+
|
|
38
|
+
// Bind methods to preserve context
|
|
39
|
+
this._onConnect = this._onConnect.bind(this);
|
|
40
|
+
this._onMessage = this._onMessage.bind(this);
|
|
41
|
+
this._onError = this._onError.bind(this);
|
|
42
|
+
this._onClose = this._onClose.bind(this);
|
|
43
|
+
this._onOffline = this._onOffline.bind(this);
|
|
44
|
+
this._onReconnect = this._onReconnect.bind(this);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Connect to MQTT broker
|
|
49
|
+
* @returns {Promise<boolean>} True if connection succeeded
|
|
50
|
+
*/
|
|
51
|
+
async connect() {
|
|
52
|
+
if (this.isConnected || this.isConnecting) {
|
|
53
|
+
return this.isConnected;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.isConnecting = true;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// Check if client is authenticated
|
|
60
|
+
if (!this.client.isLoggedIn()) {
|
|
61
|
+
throw new Error('Client must be logged in to use realtime service');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Get authorization token from session
|
|
65
|
+
const authToken = this._getAuthToken();
|
|
66
|
+
if (!authToken) {
|
|
67
|
+
throw new Error('No valid authorization token found in session');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// MQTT connection configuration
|
|
71
|
+
const mqttOptions = {
|
|
72
|
+
clientId: this.clientId,
|
|
73
|
+
username: this.username,
|
|
74
|
+
password: authToken,
|
|
75
|
+
keepalive: this.keepalive,
|
|
76
|
+
clean: this.cleanSession,
|
|
77
|
+
reconnectPeriod: 0, // Disable automatic reconnection - we handle it manually
|
|
78
|
+
connectTimeout: 30000,
|
|
79
|
+
protocolVersion: 4, // MQTT v3.1.1
|
|
80
|
+
rejectUnauthorized: true
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Broker URL
|
|
84
|
+
const brokerUrl = `${this.protocol}://${this.broker}:${this.port}`;
|
|
85
|
+
|
|
86
|
+
if (this.client.state.verbose) {
|
|
87
|
+
console.log(`[Realtime] Connecting to MQTT broker: ${brokerUrl}`);
|
|
88
|
+
console.log(`[Realtime] Client ID: ${this.clientId}`);
|
|
89
|
+
console.log(`[Realtime] Username: ${this.username}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Create MQTT connection
|
|
93
|
+
this.mqttClient = mqtt.connect(brokerUrl, mqttOptions);
|
|
94
|
+
|
|
95
|
+
// Configure event handlers
|
|
96
|
+
this.mqttClient.on('connect', this._onConnect);
|
|
97
|
+
this.mqttClient.on('message', this._onMessage);
|
|
98
|
+
this.mqttClient.on('error', this._onError);
|
|
99
|
+
this.mqttClient.on('close', this._onClose);
|
|
100
|
+
this.mqttClient.on('offline', this._onOffline);
|
|
101
|
+
this.mqttClient.on('reconnect', this._onReconnect);
|
|
102
|
+
|
|
103
|
+
// Wait for connection
|
|
104
|
+
return new Promise((resolve, reject) => {
|
|
105
|
+
const timeout = setTimeout(() => {
|
|
106
|
+
this.isConnecting = false;
|
|
107
|
+
reject(new Error('MQTT connection timeout'));
|
|
108
|
+
}, 30000);
|
|
109
|
+
|
|
110
|
+
this.mqttClient.once('connect', () => {
|
|
111
|
+
clearTimeout(timeout);
|
|
112
|
+
this.isConnecting = false;
|
|
113
|
+
resolve(true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
this.mqttClient.once('error', (err) => {
|
|
117
|
+
clearTimeout(timeout);
|
|
118
|
+
this.isConnecting = false;
|
|
119
|
+
reject(err);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
} catch (error) {
|
|
124
|
+
this.isConnecting = false;
|
|
125
|
+
if (this.client.state.verbose) {
|
|
126
|
+
console.error('[Realtime] Connection failed:', error.message);
|
|
127
|
+
}
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Disconnect from MQTT broker
|
|
134
|
+
*/
|
|
135
|
+
disconnect() {
|
|
136
|
+
if (this.mqttClient && this.isConnected) {
|
|
137
|
+
if (this.client.state.verbose) {
|
|
138
|
+
console.log('[Realtime] Disconnecting from MQTT broker...');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.mqttClient.end();
|
|
142
|
+
this.isConnected = false;
|
|
143
|
+
this.reconnectAttempts = 0;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Check if service is connected
|
|
149
|
+
* @returns {boolean}
|
|
150
|
+
*/
|
|
151
|
+
isRealtimeConnected() {
|
|
152
|
+
return this.isConnected && this.mqttClient && this.mqttClient.connected;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Send ping to broker
|
|
157
|
+
*/
|
|
158
|
+
ping() {
|
|
159
|
+
if (this.isRealtimeConnected()) {
|
|
160
|
+
if (this.client.state.verbose) {
|
|
161
|
+
console.log('[Realtime] Sending ping...');
|
|
162
|
+
}
|
|
163
|
+
// MQTT client handles ping automatically through keepalive
|
|
164
|
+
// But we can emit an event for debugging
|
|
165
|
+
this.emit('ping');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get authorization token from session
|
|
171
|
+
* @private
|
|
172
|
+
*/
|
|
173
|
+
_getAuthToken() {
|
|
174
|
+
try {
|
|
175
|
+
// Try to get from state.authorization
|
|
176
|
+
if (this.client.state.authorization) {
|
|
177
|
+
return this.client.state.authorization;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Fallback: try to get from cookies
|
|
181
|
+
const sessionId = this.client.state.getCookieValueSafe('sessionid');
|
|
182
|
+
if (sessionId) {
|
|
183
|
+
return sessionId;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return null;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
if (this.client.state.verbose) {
|
|
189
|
+
console.error('[Realtime] Error getting auth token:', error.message);
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Handler for MQTT connection
|
|
197
|
+
* @private
|
|
198
|
+
*/
|
|
199
|
+
_onConnect() {
|
|
200
|
+
this.isConnected = true;
|
|
201
|
+
this.reconnectAttempts = 0;
|
|
202
|
+
|
|
203
|
+
if (this.client.state.verbose) {
|
|
204
|
+
console.log('[Realtime] Connected to MQTT broker');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Subscribe to all topics
|
|
208
|
+
this._subscribeToTopics();
|
|
209
|
+
|
|
210
|
+
// Emit connection event
|
|
211
|
+
this.emit('connected');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Handler for MQTT messages
|
|
216
|
+
* @private
|
|
217
|
+
*/
|
|
218
|
+
_onMessage(topic, payload) {
|
|
219
|
+
try {
|
|
220
|
+
const message = payload.toString();
|
|
221
|
+
|
|
222
|
+
if (this.client.state.verbose) {
|
|
223
|
+
console.log(`[Realtime] Received message on ${topic}: ${message}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Find the topic configuration
|
|
227
|
+
const topicConfig = this._findTopicByPath(topic);
|
|
228
|
+
|
|
229
|
+
if (topicConfig && topicConfig.parser && !topicConfig.noParse) {
|
|
230
|
+
// Parse using the topic's parser
|
|
231
|
+
const parsedData = topicConfig.parser.parse(payload);
|
|
232
|
+
|
|
233
|
+
// Emit generic realtime event
|
|
234
|
+
this.emit('realtimeEvent', {
|
|
235
|
+
topic,
|
|
236
|
+
topicId: topicConfig.id,
|
|
237
|
+
data: parsedData,
|
|
238
|
+
rawPayload: message,
|
|
239
|
+
timestamp: new Date().toISOString()
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Emit specific events based on topic
|
|
243
|
+
this._emitTopicSpecificEvent(topic, parsedData);
|
|
244
|
+
} else {
|
|
245
|
+
// No parser or noParse is true, emit raw data
|
|
246
|
+
this.emit('realtimeEvent', {
|
|
247
|
+
topic,
|
|
248
|
+
topicId: topicConfig ? topicConfig.id : null,
|
|
249
|
+
data: { raw: message },
|
|
250
|
+
rawPayload: message,
|
|
251
|
+
timestamp: new Date().toISOString()
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Emit specific events based on topic
|
|
255
|
+
this._emitTopicSpecificEvent(topic, { raw: message });
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
} catch (error) {
|
|
259
|
+
if (this.client.state.verbose) {
|
|
260
|
+
console.error('[Realtime] Error processing message:', error.message);
|
|
261
|
+
}
|
|
262
|
+
this.emit('error', error);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Find topic configuration by path
|
|
268
|
+
* @private
|
|
269
|
+
*/
|
|
270
|
+
_findTopicByPath(path) {
|
|
271
|
+
return RealtimeTopicsArray.find(topic => topic.path === path);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Emit topic-specific events
|
|
276
|
+
* @private
|
|
277
|
+
*/
|
|
278
|
+
_emitTopicSpecificEvent(topic, data) {
|
|
279
|
+
switch (topic) {
|
|
280
|
+
case '/graphql':
|
|
281
|
+
this.emit('graphqlMessage', data);
|
|
282
|
+
break;
|
|
283
|
+
case '/pubsub':
|
|
284
|
+
this.emit('pubsubMessage', data);
|
|
285
|
+
break;
|
|
286
|
+
case '/ig_send_message_response':
|
|
287
|
+
this.emit('sendMessageResponse', data);
|
|
288
|
+
break;
|
|
289
|
+
case '/ig_sub_iris_response':
|
|
290
|
+
this.emit('irisSubResponse', data);
|
|
291
|
+
break;
|
|
292
|
+
case '/ig_message_sync':
|
|
293
|
+
this.emit('messageSync', data);
|
|
294
|
+
break;
|
|
295
|
+
case '/ig_realtime_sub':
|
|
296
|
+
this.emit('realtimeSub', data);
|
|
297
|
+
break;
|
|
298
|
+
case '/t_region_hint':
|
|
299
|
+
this.emit('regionHint', data);
|
|
300
|
+
break;
|
|
301
|
+
case '/t_fs':
|
|
302
|
+
this.emit('foregroundState', data);
|
|
303
|
+
break;
|
|
304
|
+
case '/ig_send_message':
|
|
305
|
+
this.emit('sendMessage', data);
|
|
306
|
+
break;
|
|
307
|
+
default:
|
|
308
|
+
this.emit('unknownMessage', { topic, data });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Handler for MQTT errors
|
|
314
|
+
* @private
|
|
315
|
+
*/
|
|
316
|
+
_onError(error) {
|
|
317
|
+
if (this.client.state.verbose) {
|
|
318
|
+
console.error('[Realtime] MQTT error:', error.message);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
this.emit('error', error);
|
|
322
|
+
|
|
323
|
+
// Try reconnection if not already in process
|
|
324
|
+
if (!this.isConnecting && this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
325
|
+
this._scheduleReconnect();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Handler for connection close
|
|
331
|
+
* @private
|
|
332
|
+
*/
|
|
333
|
+
_onClose() {
|
|
334
|
+
this.isConnected = false;
|
|
335
|
+
|
|
336
|
+
if (this.client.state.verbose) {
|
|
337
|
+
console.log('[Realtime] MQTT connection closed');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
this.emit('disconnected');
|
|
341
|
+
|
|
342
|
+
// Try reconnection if not manually disconnected
|
|
343
|
+
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
344
|
+
this._scheduleReconnect();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Handler for offline
|
|
350
|
+
* @private
|
|
351
|
+
*/
|
|
352
|
+
_onOffline() {
|
|
353
|
+
this.isConnected = false;
|
|
354
|
+
|
|
355
|
+
if (this.client.state.verbose) {
|
|
356
|
+
console.log('[Realtime] MQTT client offline');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
this.emit('offline');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Handler for reconnection
|
|
364
|
+
* @private
|
|
365
|
+
*/
|
|
366
|
+
_onReconnect() {
|
|
367
|
+
if (this.client.state.verbose) {
|
|
368
|
+
console.log('[Realtime] MQTT client reconnecting...');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
this.emit('reconnecting');
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Subscribe to all topics
|
|
376
|
+
* @private
|
|
377
|
+
*/
|
|
378
|
+
_subscribeToTopics() {
|
|
379
|
+
if (!this.isRealtimeConnected()) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
RealtimeTopicsArray.forEach(topic => {
|
|
384
|
+
this.mqttClient.subscribe(topic.path, (err) => {
|
|
385
|
+
if (err) {
|
|
386
|
+
if (this.client.state.verbose) {
|
|
387
|
+
console.error(`[Realtime] Failed to subscribe to ${topic.path}:`, err.message);
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
390
|
+
if (this.client.state.verbose) {
|
|
391
|
+
console.log(`[Realtime] Subscribed to ${topic.path}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Schedule reconnection
|
|
400
|
+
* @private
|
|
401
|
+
*/
|
|
402
|
+
_scheduleReconnect() {
|
|
403
|
+
if (this.isConnecting) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
this.reconnectAttempts++;
|
|
408
|
+
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1); // Exponential backoff
|
|
409
|
+
|
|
410
|
+
if (this.client.state.verbose) {
|
|
411
|
+
console.log(`[Realtime] Scheduling reconnect in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
setTimeout(() => {
|
|
415
|
+
if (this.reconnectAttempts <= this.maxReconnectAttempts) {
|
|
416
|
+
this.connect().catch(error => {
|
|
417
|
+
if (this.client.state.verbose) {
|
|
418
|
+
console.error('[Realtime] Reconnect failed:', error.message);
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
} else {
|
|
422
|
+
if (this.client.state.verbose) {
|
|
423
|
+
console.error('[Realtime] Max reconnect attempts reached');
|
|
424
|
+
}
|
|
425
|
+
this.emit('maxReconnectAttemptsReached');
|
|
426
|
+
}
|
|
427
|
+
}, delay);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Set reconnection options
|
|
432
|
+
* @param {Object} options - Reconnection options
|
|
433
|
+
* @param {number} options.maxAttempts - Maximum number of attempts
|
|
434
|
+
* @param {number} options.delay - Initial delay in ms
|
|
435
|
+
*/
|
|
436
|
+
setReconnectOptions(options = {}) {
|
|
437
|
+
if (typeof options.maxAttempts === 'number') {
|
|
438
|
+
this.maxReconnectAttempts = options.maxAttempts;
|
|
439
|
+
}
|
|
440
|
+
if (typeof options.delay === 'number') {
|
|
441
|
+
this.reconnectDelay = options.delay;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Get connection statistics
|
|
447
|
+
* @returns {Object} Connection statistics
|
|
448
|
+
*/
|
|
449
|
+
getStats() {
|
|
450
|
+
return {
|
|
451
|
+
isConnected: this.isRealtimeConnected(),
|
|
452
|
+
isConnecting: this.isConnecting,
|
|
453
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
454
|
+
maxReconnectAttempts: this.maxReconnectAttempts,
|
|
455
|
+
clientId: this.clientId,
|
|
456
|
+
subscribedTopics: RealtimeTopicsArray.map(t => t.path),
|
|
457
|
+
broker: `${this.protocol}://${this.broker}:${this.port}`
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
module.exports = RealtimeService;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const debug = require('debug')('ig:reconnect');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reconnect Manager - Exponential backoff untuk MQTT reconnection
|
|
5
|
+
*/
|
|
6
|
+
class ReconnectManager {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.initialDelay = options.initialDelay || 1000; // 1s
|
|
9
|
+
this.maxDelay = options.maxDelay || 30000; // 30s
|
|
10
|
+
this.multiplier = options.multiplier || 2;
|
|
11
|
+
this.maxAttempts = options.maxAttempts || 0; // 0 = unlimited
|
|
12
|
+
|
|
13
|
+
this.currentAttempt = 0;
|
|
14
|
+
this.currentDelay = this.initialDelay;
|
|
15
|
+
this.timerId = null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get next delay using exponential backoff
|
|
20
|
+
* 1s -> 2s -> 4s -> 8s -> 16s -> 30s (max)
|
|
21
|
+
*/
|
|
22
|
+
getNextDelay() {
|
|
23
|
+
if (this.currentAttempt === 0) {
|
|
24
|
+
this.currentDelay = this.initialDelay;
|
|
25
|
+
} else {
|
|
26
|
+
this.currentDelay = Math.min(
|
|
27
|
+
this.currentDelay * this.multiplier,
|
|
28
|
+
this.maxDelay
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.currentAttempt++;
|
|
33
|
+
|
|
34
|
+
debug(`Reconnect attempt #${this.currentAttempt}, next delay: ${this.currentDelay}ms`);
|
|
35
|
+
|
|
36
|
+
return this.currentDelay;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Schedule reconnection after calculated delay
|
|
41
|
+
*/
|
|
42
|
+
scheduleReconnect(callback) {
|
|
43
|
+
if (this.maxAttempts > 0 && this.currentAttempt >= this.maxAttempts) {
|
|
44
|
+
debug('[RECONNECT] Max reconnection attempts reached');
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const delay = this.getNextDelay();
|
|
49
|
+
|
|
50
|
+
this.timerId = setTimeout(() => {
|
|
51
|
+
debug(`[RECONNECT] Reconnecting... (attempt ${this.currentAttempt})`);
|
|
52
|
+
callback();
|
|
53
|
+
}, delay);
|
|
54
|
+
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Reset on successful connection
|
|
60
|
+
*/
|
|
61
|
+
reset() {
|
|
62
|
+
if (this.timerId) {
|
|
63
|
+
clearTimeout(this.timerId);
|
|
64
|
+
this.timerId = null;
|
|
65
|
+
}
|
|
66
|
+
this.currentAttempt = 0;
|
|
67
|
+
this.currentDelay = this.initialDelay;
|
|
68
|
+
debug('[RECONNECT] Manager reset');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Cancel pending reconnection
|
|
73
|
+
*/
|
|
74
|
+
cancel() {
|
|
75
|
+
if (this.timerId) {
|
|
76
|
+
clearTimeout(this.timerId);
|
|
77
|
+
this.timerId = null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get current state for debugging
|
|
83
|
+
*/
|
|
84
|
+
getState() {
|
|
85
|
+
return {
|
|
86
|
+
currentAttempt: this.currentAttempt,
|
|
87
|
+
currentDelay: this.currentDelay,
|
|
88
|
+
maxAttempts: this.maxAttempts,
|
|
89
|
+
pending: !!this.timerId
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = ReconnectManager;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const debug = require('debug')('ig:session');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Session Manager - Persistence pentru MQTT sessions
|
|
7
|
+
* Saves: sessionid, mqtt_session_id, subscription state, seq-ids
|
|
8
|
+
*/
|
|
9
|
+
class SessionManager {
|
|
10
|
+
constructor(storageFile = '.ig-mqtt-session.json') {
|
|
11
|
+
this.storageFile = storageFile;
|
|
12
|
+
this.data = {
|
|
13
|
+
sessionId: null,
|
|
14
|
+
mqttSessionId: null,
|
|
15
|
+
subscriptions: {},
|
|
16
|
+
seqIds: {},
|
|
17
|
+
topicAcks: {},
|
|
18
|
+
lastConnected: null,
|
|
19
|
+
reconnectAttempts: 0
|
|
20
|
+
};
|
|
21
|
+
this.load();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
load() {
|
|
25
|
+
try {
|
|
26
|
+
if (fs.existsSync(this.storageFile)) {
|
|
27
|
+
const raw = fs.readFileSync(this.storageFile, 'utf-8');
|
|
28
|
+
this.data = JSON.parse(raw);
|
|
29
|
+
debug('✓ Session loaded from disk');
|
|
30
|
+
}
|
|
31
|
+
} catch (e) {
|
|
32
|
+
debug('Session load error (first run?):', e.message);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
save() {
|
|
37
|
+
try {
|
|
38
|
+
fs.writeFileSync(this.storageFile, JSON.stringify(this.data, null, 2));
|
|
39
|
+
debug('✓ Session saved');
|
|
40
|
+
} catch (e) {
|
|
41
|
+
debug('Session save error:', e.message);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
setSessionId(sessionId) {
|
|
46
|
+
this.data.sessionId = sessionId;
|
|
47
|
+
this.save();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getSessionId() {
|
|
51
|
+
return this.data.sessionId;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
setMqttSessionId(mqttSessionId) {
|
|
55
|
+
this.data.mqttSessionId = mqttSessionId;
|
|
56
|
+
this.data.lastConnected = new Date().toISOString();
|
|
57
|
+
this.save();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getMqttSessionId() {
|
|
61
|
+
return this.data.mqttSessionId;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
recordSubscription(topic, qos = 1) {
|
|
65
|
+
this.data.subscriptions[topic] = {
|
|
66
|
+
qos,
|
|
67
|
+
subscribedAt: new Date().toISOString()
|
|
68
|
+
};
|
|
69
|
+
this.save();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
getSubscriptions() {
|
|
73
|
+
return Object.keys(this.data.subscriptions);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
recordSeqId(topic, seqId) {
|
|
77
|
+
this.data.seqIds[topic] = seqId;
|
|
78
|
+
this.save();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getSeqId(topic) {
|
|
82
|
+
return this.data.seqIds[topic] || 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
recordAck(topic, msgId) {
|
|
86
|
+
this.data.topicAcks[topic] = {
|
|
87
|
+
msgId,
|
|
88
|
+
ackedAt: new Date().toISOString()
|
|
89
|
+
};
|
|
90
|
+
this.save();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
recordReconnectAttempt() {
|
|
94
|
+
this.data.reconnectAttempts++;
|
|
95
|
+
this.save();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
resetReconnectAttempts() {
|
|
99
|
+
this.data.reconnectAttempts = 0;
|
|
100
|
+
this.save();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
getReconnectAttempts() {
|
|
104
|
+
return this.data.reconnectAttempts;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
clear() {
|
|
108
|
+
this.data = {
|
|
109
|
+
sessionId: null,
|
|
110
|
+
mqttSessionId: null,
|
|
111
|
+
subscriptions: {},
|
|
112
|
+
seqIds: {},
|
|
113
|
+
topicAcks: {},
|
|
114
|
+
lastConnected: null,
|
|
115
|
+
reconnectAttempts: 0
|
|
116
|
+
};
|
|
117
|
+
this.save();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = SessionManager;
|