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.
Files changed (210) hide show
  1. package/README.md +1650 -0
  2. package/dist/constants/constants.js +280 -0
  3. package/dist/constants/index.js +41 -0
  4. package/dist/core/client.js +243 -0
  5. package/dist/core/repository.js +7 -0
  6. package/dist/core/request.js +212 -0
  7. package/dist/core/state.js +1456 -0
  8. package/dist/core/utils.js +786 -0
  9. package/dist/downloadMedia.js +381 -0
  10. package/dist/errors/index.d.ts +16 -0
  11. package/dist/errors/index.js +30 -0
  12. package/dist/errors/index.js.map +1 -0
  13. package/dist/fbns/fbns.client.d.ts +32 -0
  14. package/dist/fbns/fbns.client.events.d.ts +41 -0
  15. package/dist/fbns/fbns.client.events.js +3 -0
  16. package/dist/fbns/fbns.client.events.js.map +1 -0
  17. package/dist/fbns/fbns.client.js +179 -0
  18. package/dist/fbns/fbns.client.js.map +1 -0
  19. package/dist/fbns/fbns.device-auth.d.ts +17 -0
  20. package/dist/fbns/fbns.device-auth.js +54 -0
  21. package/dist/fbns/fbns.device-auth.js.map +1 -0
  22. package/dist/fbns/fbns.types.d.ts +83 -0
  23. package/dist/fbns/fbns.types.js +3 -0
  24. package/dist/fbns/fbns.types.js.map +1 -0
  25. package/dist/fbns/fbns.utilities.d.ts +2 -0
  26. package/dist/fbns/fbns.utilities.js +79 -0
  27. package/dist/fbns/fbns.utilities.js.map +1 -0
  28. package/dist/fbns/index.d.ts +4 -0
  29. package/dist/fbns/index.js +21 -0
  30. package/dist/fbns/index.js.map +1 -0
  31. package/dist/index.js +39 -0
  32. package/dist/mqttot/index.d.ts +4 -0
  33. package/dist/mqttot/index.js +21 -0
  34. package/dist/mqttot/index.js.map +1 -0
  35. package/dist/mqttot/mqttot.client.d.ts +39 -0
  36. package/dist/mqttot/mqttot.client.js +120 -0
  37. package/dist/mqttot/mqttot.client.js.map +1 -0
  38. package/dist/mqttot/mqttot.connect.request.packet.d.ts +7 -0
  39. package/dist/mqttot/mqttot.connect.request.packet.js +9 -0
  40. package/dist/mqttot/mqttot.connect.request.packet.js.map +1 -0
  41. package/dist/mqttot/mqttot.connect.response.packet.d.ts +7 -0
  42. package/dist/mqttot/mqttot.connect.response.packet.js +24 -0
  43. package/dist/mqttot/mqttot.connect.response.packet.js.map +1 -0
  44. package/dist/mqttot/mqttot.connection.d.ts +57 -0
  45. package/dist/mqttot/mqttot.connection.js +56 -0
  46. package/dist/mqttot/mqttot.connection.js.map +1 -0
  47. package/dist/package.json +59 -0
  48. package/dist/realtime/commands/commands.d.ts +15 -0
  49. package/dist/realtime/commands/commands.js +21 -0
  50. package/dist/realtime/commands/commands.js.map +1 -0
  51. package/dist/realtime/commands/direct.commands.d.ts +75 -0
  52. package/dist/realtime/commands/direct.commands.js +186 -0
  53. package/dist/realtime/commands/direct.commands.js.map +1 -0
  54. package/dist/realtime/commands/enhanced.direct.commands.js +987 -0
  55. package/dist/realtime/commands/index.d.ts +2 -0
  56. package/dist/realtime/commands/index.js +19 -0
  57. package/dist/realtime/commands/index.js.map +1 -0
  58. package/dist/realtime/delta-sync.manager.js +293 -0
  59. package/dist/realtime/features/dm-sender.js +88 -0
  60. package/dist/realtime/features/error-handler.js +73 -0
  61. package/dist/realtime/features/gap-handler.js +61 -0
  62. package/dist/realtime/features/presence.manager.js +66 -0
  63. package/dist/realtime/index.js +30 -0
  64. package/dist/realtime/messages/app-presence.event.d.ts +9 -0
  65. package/dist/realtime/messages/app-presence.event.js +3 -0
  66. package/dist/realtime/messages/app-presence.event.js.map +1 -0
  67. package/dist/realtime/messages/index.d.ts +3 -0
  68. package/dist/realtime/messages/index.js +20 -0
  69. package/dist/realtime/messages/index.js.map +1 -0
  70. package/dist/realtime/messages/message-sync.message.d.ts +222 -0
  71. package/dist/realtime/messages/message-sync.message.js +43 -0
  72. package/dist/realtime/messages/message-sync.message.js.map +1 -0
  73. package/dist/realtime/messages/realtime-sub.direct.data.d.ts +11 -0
  74. package/dist/realtime/messages/realtime-sub.direct.data.js +3 -0
  75. package/dist/realtime/messages/realtime-sub.direct.data.js.map +1 -0
  76. package/dist/realtime/messages/thread-update.message.d.ts +68 -0
  77. package/dist/realtime/messages/thread-update.message.js +3 -0
  78. package/dist/realtime/messages/thread-update.message.js.map +1 -0
  79. package/dist/realtime/mixins/index.d.ts +3 -0
  80. package/dist/realtime/mixins/index.js +20 -0
  81. package/dist/realtime/mixins/index.js.map +1 -0
  82. package/dist/realtime/mixins/message-sync.mixin.d.ts +8 -0
  83. package/dist/realtime/mixins/message-sync.mixin.js +381 -0
  84. package/dist/realtime/mixins/message-sync.mixin.js.map +1 -0
  85. package/dist/realtime/mixins/mixin.d.ts +19 -0
  86. package/dist/realtime/mixins/mixin.js +41 -0
  87. package/dist/realtime/mixins/mixin.js.map +1 -0
  88. package/dist/realtime/mixins/presence-typing.mixin.js +33 -0
  89. package/dist/realtime/mixins/realtime-sub.mixin.d.ts +8 -0
  90. package/dist/realtime/mixins/realtime-sub.mixin.js +55 -0
  91. package/dist/realtime/mixins/realtime-sub.mixin.js.map +1 -0
  92. package/dist/realtime/parsers/graphql-parser.js +43 -0
  93. package/dist/realtime/parsers/graphql.parser.d.ts +15 -0
  94. package/dist/realtime/parsers/graphql.parser.js +22 -0
  95. package/dist/realtime/parsers/graphql.parser.js.map +1 -0
  96. package/dist/realtime/parsers/index.d.ts +6 -0
  97. package/dist/realtime/parsers/index.js +23 -0
  98. package/dist/realtime/parsers/index.js.map +1 -0
  99. package/dist/realtime/parsers/iris-parser.js +43 -0
  100. package/dist/realtime/parsers/iris.parser.d.ts +17 -0
  101. package/dist/realtime/parsers/iris.parser.js +10 -0
  102. package/dist/realtime/parsers/iris.parser.js.map +1 -0
  103. package/dist/realtime/parsers/json-parser.js +43 -0
  104. package/dist/realtime/parsers/json.parser.d.ts +6 -0
  105. package/dist/realtime/parsers/json.parser.js +10 -0
  106. package/dist/realtime/parsers/json.parser.js.map +1 -0
  107. package/dist/realtime/parsers/parser.d.ts +9 -0
  108. package/dist/realtime/parsers/parser.js +3 -0
  109. package/dist/realtime/parsers/parser.js.map +1 -0
  110. package/dist/realtime/parsers/region-hint-parser.js +43 -0
  111. package/dist/realtime/parsers/region-hint.parser.d.ts +12 -0
  112. package/dist/realtime/parsers/region-hint.parser.js +15 -0
  113. package/dist/realtime/parsers/region-hint.parser.js.map +1 -0
  114. package/dist/realtime/parsers/skywalker-parser.js +43 -0
  115. package/dist/realtime/parsers/skywalker.parser.d.ts +12 -0
  116. package/dist/realtime/parsers/skywalker.parser.js +15 -0
  117. package/dist/realtime/parsers/skywalker.parser.js.map +1 -0
  118. package/dist/realtime/parsers-advanced.js +158 -0
  119. package/dist/realtime/proto/common.proto +38 -0
  120. package/dist/realtime/proto/direct.proto +65 -0
  121. package/dist/realtime/proto/ig-messages.proto +83 -0
  122. package/dist/realtime/proto/iris.proto +188 -0
  123. package/dist/realtime/proto-parser.js +195 -0
  124. package/dist/realtime/protocols/iris.handshake.js +74 -0
  125. package/dist/realtime/protocols/proto-definitions.js +80 -0
  126. package/dist/realtime/protocols/skywalker.protocol.js +91 -0
  127. package/dist/realtime/realtime.client.events.js +3 -0
  128. package/dist/realtime/realtime.client.js +449 -0
  129. package/dist/realtime/realtime.service.js +462 -0
  130. package/dist/realtime/reconnect.manager.js +94 -0
  131. package/dist/realtime/session.manager.js +121 -0
  132. package/dist/realtime/subscriptions/graphql.subscription.d.ts +47 -0
  133. package/dist/realtime/subscriptions/graphql.subscription.js +99 -0
  134. package/dist/realtime/subscriptions/graphql.subscription.js.map +1 -0
  135. package/dist/realtime/subscriptions/index.d.ts +2 -0
  136. package/dist/realtime/subscriptions/index.js +19 -0
  137. package/dist/realtime/subscriptions/index.js.map +1 -0
  138. package/dist/realtime/subscriptions/skywalker.subscription.d.ts +4 -0
  139. package/dist/realtime/subscriptions/skywalker.subscription.js +13 -0
  140. package/dist/realtime/subscriptions/skywalker.subscription.js.map +1 -0
  141. package/dist/realtime/topic-map.js +71 -0
  142. package/dist/realtime/topic.js +80 -0
  143. package/dist/repositories/account.repository.js +261 -0
  144. package/dist/repositories/direct-thread.repository.js +247 -0
  145. package/dist/repositories/direct.repository.js +153 -0
  146. package/dist/repositories/feed.repository.js +233 -0
  147. package/dist/repositories/friendship.repository.js +190 -0
  148. package/dist/repositories/hashtag.repository.js +101 -0
  149. package/dist/repositories/highlights.repository.js +127 -0
  150. package/dist/repositories/location.repository.js +84 -0
  151. package/dist/repositories/media.repository.js +165 -0
  152. package/dist/repositories/story.repository.js +156 -0
  153. package/dist/repositories/upload.repository.js +167 -0
  154. package/dist/repositories/user.repository.js +94 -0
  155. package/dist/sendmedia/index.js +11 -0
  156. package/dist/sendmedia/sendFile.js +154 -0
  157. package/dist/sendmedia/sendPhoto.js +145 -0
  158. package/dist/sendmedia/uploadPhoto.js +175 -0
  159. package/dist/sendmedia/uploadfFile.js +264 -0
  160. package/dist/services/live.service.js +147 -0
  161. package/dist/services/search.service.js +116 -0
  162. package/dist/shared/index.js +35 -0
  163. package/dist/shared/shared.js +86 -0
  164. package/dist/thrift/index.d.ts +3 -0
  165. package/dist/thrift/index.js +20 -0
  166. package/dist/thrift/index.js.map +1 -0
  167. package/dist/thrift/thrift.d.ts +59 -0
  168. package/dist/thrift/thrift.js +101 -0
  169. package/dist/thrift/thrift.js.map +1 -0
  170. package/dist/thrift/thrift.reading.d.ts +41 -0
  171. package/dist/thrift/thrift.reading.js +327 -0
  172. package/dist/thrift/thrift.reading.js.map +1 -0
  173. package/dist/thrift/thrift.writing.d.ts +44 -0
  174. package/dist/thrift/thrift.writing.js +342 -0
  175. package/dist/thrift/thrift.writing.js.map +1 -0
  176. package/dist/types/index.js +285 -0
  177. package/dist/useMultiFileAuthState.js +437 -0
  178. package/dist/utils/helper-1.js +1 -0
  179. package/dist/utils/helper-10.js +1 -0
  180. package/dist/utils/helper-11.js +1 -0
  181. package/dist/utils/helper-12.js +1 -0
  182. package/dist/utils/helper-13.js +1 -0
  183. package/dist/utils/helper-14.js +1 -0
  184. package/dist/utils/helper-15.js +1 -0
  185. package/dist/utils/helper-16.js +1 -0
  186. package/dist/utils/helper-17.js +1 -0
  187. package/dist/utils/helper-18.js +1 -0
  188. package/dist/utils/helper-19.js +1 -0
  189. package/dist/utils/helper-2.js +1 -0
  190. package/dist/utils/helper-20.js +1 -0
  191. package/dist/utils/helper-21.js +1 -0
  192. package/dist/utils/helper-22.js +1 -0
  193. package/dist/utils/helper-23.js +1 -0
  194. package/dist/utils/helper-24.js +1 -0
  195. package/dist/utils/helper-25.js +1 -0
  196. package/dist/utils/helper-26.js +1 -0
  197. package/dist/utils/helper-27.js +1 -0
  198. package/dist/utils/helper-28.js +1 -0
  199. package/dist/utils/helper-29.js +1 -0
  200. package/dist/utils/helper-3.js +1 -0
  201. package/dist/utils/helper-30.js +1 -0
  202. package/dist/utils/helper-4.js +1 -0
  203. package/dist/utils/helper-5.js +1 -0
  204. package/dist/utils/helper-6.js +1 -0
  205. package/dist/utils/helper-7.js +1 -0
  206. package/dist/utils/helper-8.js +1 -0
  207. package/dist/utils/helper-9.js +1 -0
  208. package/dist/utils/index.js +280 -0
  209. package/examples/listen-to-messages.js +86 -0
  210. 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;