webmaxsocket 1.0.0 → 1.1.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/lib/client.js CHANGED
@@ -1,13 +1,35 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
1
3
  const WebSocket = require('ws');
2
4
  const EventEmitter = require('events');
3
5
  const { v4: uuidv4 } = require('uuid');
4
6
  const qrcode = require('qrcode-terminal');
5
7
  const SessionManager = require('./session');
8
+ const { MaxSocketTransport } = require('./socketTransport');
6
9
  const { Message, ChatAction, User } = require('./entities');
7
10
  const { EventTypes, ChatActions } = require('./constants');
8
11
  const { Opcode, DeviceType, getOpcodeName } = require('./opcodes');
9
12
  const { UserAgentPayload } = require('./userAgent');
10
13
 
14
+ /**
15
+ * Загружает конфиг: { token, agent }
16
+ */
17
+ function loadSessionConfig(configPath) {
18
+ let resolved;
19
+ if (path.isAbsolute(configPath)) {
20
+ resolved = configPath;
21
+ } else if (!/[\\/]/.test(configPath) && !configPath.endsWith('.json')) {
22
+ resolved = path.join(process.cwd(), 'config', `${configPath}.json`);
23
+ } else {
24
+ resolved = path.join(process.cwd(), configPath);
25
+ }
26
+ if (!fs.existsSync(resolved)) {
27
+ throw new Error(`Конфиг не найден: ${resolved}`);
28
+ }
29
+ const data = fs.readFileSync(resolved, 'utf8');
30
+ return JSON.parse(data);
31
+ }
32
+
11
33
  /**
12
34
  * Основной клиент для работы с API Max
13
35
  */
@@ -18,20 +40,53 @@ class WebMaxClient extends EventEmitter {
18
40
  this.phone = options.phone || null;
19
41
  this.sessionName = options.name || options.session || 'default';
20
42
  this.apiUrl = options.apiUrl || 'wss://ws-api.oneme.ru/websocket';
43
+
44
+ // Загрузка из config — token, ua (agent), device_type
45
+ let token = options.token || null;
46
+ let agent = options.ua || options.agent || options.headerUserAgent || null;
47
+ let configObj = {};
48
+ const configPath = options.configPath || options.config;
49
+ if (configPath) {
50
+ configObj = loadSessionConfig(configPath);
51
+ token = token || configObj.token || null;
52
+ agent = agent || configObj.agent || configObj.ua || configObj.headerUserAgent || null;
53
+ }
54
+
55
+ this._providedToken = token;
56
+ this._saveTokenToSession = options.saveToken !== false;
21
57
  this.origin = 'https://web.max.ru';
22
58
  this.session = new SessionManager(this.sessionName);
23
59
 
24
- // UserAgent
25
- this.userAgent = options.userAgent || new UserAgentPayload({
26
- appVersion: options.appVersion || '25.12.14'
27
- });
60
+ const deviceTypeMap = { 1: 'WEB', 2: 'IOS', 3: 'ANDROID' };
61
+ const rawDeviceType = options.deviceType ?? configObj.device_type ?? configObj.deviceType ?? this.session.get('deviceType');
62
+ const deviceType = deviceTypeMap[rawDeviceType] || rawDeviceType || 'WEB';
63
+ const uaString = agent || configObj.headerUserAgent || configObj.ua || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36';
64
+ const webDefaults = {
65
+ deviceType: deviceType,
66
+ locale: configObj.locale || 'ru',
67
+ deviceLocale: configObj.deviceLocale || configObj.locale || 'ru',
68
+ osVersion: configObj.osVersion || (deviceType === 'IOS' ? '18.6.2' : deviceType === 'ANDROID' ? '14' : 'Linux'),
69
+ deviceName: configObj.deviceName || (deviceType === 'IOS' ? 'Safari' : deviceType === 'ANDROID' ? 'Chrome' : 'Chrome'),
70
+ headerUserAgent: uaString,
71
+ appVersion: configObj.appVersion || '25.10.5',
72
+ screen: configObj.screen || (deviceType === 'IOS' ? '390x844 3.0x' : deviceType === 'ANDROID' ? '360x780 3.0x' : '1080x1920 1.0x'),
73
+ timezone: configObj.timezone || 'Europe/Moscow',
74
+ buildNumber: configObj.buildNumber,
75
+ clientSessionId: configObj.clientSessionId || this.session.get('clientSessionId'),
76
+ release: configObj.release
77
+ };
78
+ this._handshakeUserAgent = new UserAgentPayload(webDefaults);
79
+ this.userAgent = this._handshakeUserAgent;
28
80
 
29
- // Device ID
30
81
  this.deviceId = options.deviceId || this.session.get('deviceId') || uuidv4();
31
82
  if (!this.session.get('deviceId')) {
32
83
  this.session.set('deviceId', this.deviceId);
33
84
  }
34
85
 
86
+ // Определяем тип транспорта: Socket для IOS/ANDROID, WebSocket для WEB
87
+ this._useSocketTransport = (deviceType === 'IOS' || deviceType === 'ANDROID');
88
+ this._socketTransport = null;
89
+
35
90
  this.ws = null;
36
91
  this.me = null;
37
92
  this.isConnected = false;
@@ -55,6 +110,7 @@ class WebMaxClient extends EventEmitter {
55
110
 
56
111
  this.messageQueue = [];
57
112
  this.pendingRequests = new Map();
113
+ this.debug = options.debug || process.env.DEBUG === '1';
58
114
  }
59
115
 
60
116
  /**
@@ -135,22 +191,35 @@ class WebMaxClient extends EventEmitter {
135
191
  try {
136
192
  console.log('🚀 Запуск WebMax клиента...');
137
193
 
138
- // Подключаемся к WebSocket
194
+ // Подключаемся к WebSocket или Socket
139
195
  await this.connect();
140
196
 
141
- // Проверяем наличие сохраненного токена
142
- const savedToken = this.session.get('token');
197
+ // Приоритет: 1) переданный токен, 2) сохранённая сессия, 3) QR-авторизация
198
+ const tokenToUse = this._providedToken || this.session.get('token');
143
199
 
144
- if (savedToken) {
145
- console.log('✅ Найдена сохраненная сессия');
146
- this._token = savedToken;
200
+ if (tokenToUse) {
201
+ if (this._providedToken) {
202
+ console.log('✅ Вход по токену (token auth)');
203
+ if (this._saveTokenToSession) {
204
+ this.session.set('token', this._providedToken);
205
+ this.session.set('deviceId', this.deviceId);
206
+ }
207
+ } else {
208
+ console.log('✅ Найдена сохраненная сессия');
209
+ }
210
+ this._token = tokenToUse;
147
211
 
148
212
  try {
149
213
  await this.sync();
150
214
  this.isAuthorized = true;
151
215
  } catch (error) {
152
- console.log('⚠️ Сессия истекла, требуется повторная авторизация');
216
+ const wasTokenAuth = !!this._providedToken;
153
217
  this.session.clear();
218
+ this._providedToken = null;
219
+ if (wasTokenAuth) {
220
+ throw new Error(`Токен недействителен или сессия истекла. Обновите токен в config. (${error.message})`);
221
+ }
222
+ console.log('⚠️ Сессия истекла, требуется повторная авторизация');
154
223
  await this.authorize();
155
224
  }
156
225
  } else {
@@ -288,12 +357,25 @@ class WebMaxClient extends EventEmitter {
288
357
  throw new Error('Токен не получен из ответа');
289
358
  }
290
359
 
360
+ // Сохраняем токен и все данные сессии для TCP подключения
291
361
  this.session.set('token', token);
292
362
  this.session.set('deviceId', this.deviceId);
363
+ this.session.set('clientSessionId', this.userAgent.clientSessionId);
364
+ this.session.set('deviceType', 'IOS'); // Переключаемся на IOS для TCP при следующем запуске
365
+ this.session.set('headerUserAgent', this.userAgent.headerUserAgent);
366
+ this.session.set('appVersion', this.userAgent.appVersion);
367
+ this.session.set('osVersion', this.userAgent.osVersion);
368
+ this.session.set('deviceName', this.userAgent.deviceName);
369
+ this.session.set('screen', this.userAgent.screen);
370
+ this.session.set('timezone', this.userAgent.timezone);
371
+ this.session.set('locale', this.userAgent.locale);
372
+ this.session.set('buildNumber', this.userAgent.buildNumber);
373
+
293
374
  this.isAuthorized = true;
294
375
  this._token = token;
295
376
 
296
377
  console.log('✅ Авторизация через QR-код успешна!');
378
+ console.log('💡 При следующем запуске будет использоваться TCP Socket транспорт');
297
379
 
298
380
  // Выполняем sync
299
381
  await this.sync();
@@ -305,11 +387,105 @@ class WebMaxClient extends EventEmitter {
305
387
  }
306
388
 
307
389
  /**
308
- * Авторизация пользователя через QR-код
390
+ * Авторизация по номеру телефона через SMS (для IOS/ANDROID)
309
391
  */
310
- async authorize() {
311
- console.log('🔐 Авторизация через QR-код');
312
- await this.authorizeByQR();
392
+ async authorizeBySMS(phone) {
393
+ if (!this._useSocketTransport) {
394
+ throw new Error('SMS авторизация доступна только для IOS/ANDROID (используйте deviceType: "IOS" или "ANDROID")');
395
+ }
396
+
397
+ try {
398
+ console.log('📱 Авторизация по номеру телефона...');
399
+
400
+ // Нормализация номера телефона
401
+ let cleanPhone = phone.replace(/\D/g, '');
402
+ if (cleanPhone.startsWith('8') && cleanPhone.length === 11) {
403
+ cleanPhone = '7' + cleanPhone.slice(1);
404
+ } else if (cleanPhone.startsWith('9') && cleanPhone.length === 10) {
405
+ cleanPhone = '7' + cleanPhone;
406
+ }
407
+ const normalizedPhone = '+' + cleanPhone;
408
+
409
+ console.log(`📤 Запрос кода на номер: ${normalizedPhone}`);
410
+
411
+ if (!this._socketTransport) {
412
+ throw new Error('Socket транспорт не инициализирован');
413
+ }
414
+
415
+ // Запрос кода
416
+ const tempToken = await this._socketTransport.requestCode(normalizedPhone);
417
+
418
+ if (!tempToken) {
419
+ throw new Error('Не получен временный токен');
420
+ }
421
+
422
+ console.log('✅ Код отправлен! Ожидаем ввода кода...');
423
+
424
+ return {
425
+ tempToken,
426
+ phone: normalizedPhone,
427
+ sendCode: async (code) => {
428
+ console.log('🔐 Проверка кода...');
429
+
430
+ const authResponse = await this._socketTransport.sendCode(tempToken, code);
431
+
432
+ if (authResponse?.passwordChallenge) {
433
+ throw new Error('2FA не поддерживается');
434
+ }
435
+
436
+ const token = authResponse?.tokenAttrs?.LOGIN?.token;
437
+
438
+ if (!token) {
439
+ throw new Error('Токен не получен из ответа');
440
+ }
441
+
442
+ // Сохраняем сессию
443
+ this.session.set('token', token);
444
+ this.session.set('deviceId', this.deviceId);
445
+ this.session.set('clientSessionId', this.userAgent.clientSessionId);
446
+ this.session.set('deviceType', this.userAgent.deviceType);
447
+ this.session.set('headerUserAgent', this.userAgent.headerUserAgent);
448
+ this.session.set('appVersion', this.userAgent.appVersion);
449
+ this.session.set('osVersion', this.userAgent.osVersion);
450
+ this.session.set('deviceName', this.userAgent.deviceName);
451
+ this.session.set('screen', this.userAgent.screen);
452
+ this.session.set('timezone', this.userAgent.timezone);
453
+ this.session.set('locale', this.userAgent.locale);
454
+ this.session.set('buildNumber', this.userAgent.buildNumber);
455
+
456
+ this.isAuthorized = true;
457
+ this._token = token;
458
+
459
+ console.log('✅ Авторизация по SMS успешна!');
460
+
461
+ // Выполняем sync
462
+ await this.sync();
463
+
464
+ return token;
465
+ }
466
+ };
467
+
468
+ } catch (error) {
469
+ console.error('Ошибка SMS авторизации:', error);
470
+ throw error;
471
+ }
472
+ }
473
+
474
+ /**
475
+ * Авторизация пользователя (QR-код для WEB, SMS для IOS/ANDROID)
476
+ */
477
+ async authorize(phone = null) {
478
+ if (this._useSocketTransport && phone) {
479
+ // SMS авторизация для IOS/ANDROID
480
+ console.log('🔐 Авторизация через SMS');
481
+ return await this.authorizeBySMS(phone);
482
+ } else if (this._useSocketTransport && !phone) {
483
+ throw new Error('Для IOS/ANDROID требуется номер телефона. Используйте: authorize("+79001234567")');
484
+ } else {
485
+ // QR авторизация для WEB
486
+ console.log('🔐 Авторизация через QR-код');
487
+ await this.authorizeByQR();
488
+ }
313
489
  }
314
490
 
315
491
 
@@ -332,14 +508,16 @@ class WebMaxClient extends EventEmitter {
332
508
  contactsSync: 0,
333
509
  presenceSync: 0,
334
510
  draftsSync: 0,
335
- chatsCount: 40,
336
- userAgent: this.userAgent.toJSON()
511
+ chatsCount: 40
337
512
  };
513
+ payload.userAgent = this.userAgent.toJSON();
338
514
 
339
515
  const response = await this.sendAndWait(Opcode.LOGIN, payload);
340
516
 
341
517
  if (response.payload && response.payload.error) {
342
- throw new Error(`Sync error: ${JSON.stringify(response.payload.error)}`);
518
+ const err = response.payload.error;
519
+ const msg = typeof err === 'string' ? err : (response.payload.localizedMessage || JSON.stringify(err));
520
+ throw new Error(msg);
343
521
  }
344
522
 
345
523
  // Сохраняем информацию о пользователе
@@ -421,17 +599,58 @@ class WebMaxClient extends EventEmitter {
421
599
 
422
600
 
423
601
  /**
424
- * Установка WebSocket соединения
602
+ * Установка соединения (WebSocket или Socket)
425
603
  */
426
604
  async connect() {
605
+ if (this._useSocketTransport) {
606
+ return this._connectSocket();
607
+ } else {
608
+ return this._connectWebSocket();
609
+ }
610
+ }
611
+
612
+ /**
613
+ * Подключение через TCP Socket (для IOS/ANDROID)
614
+ */
615
+ async _connectSocket() {
616
+ if (this._socketTransport && this._socketTransport.socket && !this._socketTransport.socket.destroyed) {
617
+ this.isConnected = true;
618
+ return;
619
+ }
620
+
621
+ this._socketTransport = new MaxSocketTransport({
622
+ deviceId: this.deviceId,
623
+ deviceType: this.userAgent.deviceType,
624
+ ua: this.userAgent.headerUserAgent,
625
+ debug: this.debug
626
+ });
627
+
628
+ this._socketTransport.onNotification = (data) => {
629
+ this.handleSocketNotification(data);
630
+ };
631
+
632
+ await this._socketTransport.connect();
633
+ await this._socketTransport.handshake(this.userAgent);
634
+
635
+ this.isConnected = true;
636
+ this.reconnectAttempts = 0;
637
+ this.emit('connected');
638
+
639
+ console.log('TCP Socket соединение установлено');
640
+ }
641
+
642
+ /**
643
+ * Установка WebSocket соединения (для WEB)
644
+ */
645
+ async _connectWebSocket() {
427
646
  if (this.ws && this.isConnected) {
428
647
  return;
429
648
  }
430
649
 
431
650
  return new Promise((resolve, reject) => {
432
651
  const headers = {
433
- 'User-Agent': this.userAgent.headerUserAgent,
434
- 'Origin': this.origin
652
+ 'Origin': this.origin,
653
+ 'User-Agent': this._handshakeUserAgent.headerUserAgent
435
654
  };
436
655
 
437
656
  this.ws = new WebSocket(this.apiUrl, {
@@ -445,7 +664,6 @@ class WebMaxClient extends EventEmitter {
445
664
  this.emit('connected');
446
665
 
447
666
  try {
448
- // Выполняем handshake
449
667
  await this.handshake();
450
668
  resolve();
451
669
  } catch (error) {
@@ -466,6 +684,12 @@ class WebMaxClient extends EventEmitter {
466
684
  this.ws.on('close', () => {
467
685
  console.log('WebSocket соединение закрыто');
468
686
  this.isConnected = false;
687
+ const err = new Error('Соединение закрыто');
688
+ for (const [, pending] of this.pendingRequests) {
689
+ if (pending.timeoutId) clearTimeout(pending.timeoutId);
690
+ pending.reject(err);
691
+ }
692
+ this.pendingRequests.clear();
469
693
  this.triggerHandlers(EventTypes.DISCONNECT);
470
694
  this.handleReconnect();
471
695
  });
@@ -480,7 +704,7 @@ class WebMaxClient extends EventEmitter {
480
704
 
481
705
  const payload = {
482
706
  deviceId: this.deviceId,
483
- userAgent: this.userAgent.toJSON()
707
+ userAgent: this._handshakeUserAgent.toJSON()
484
708
  };
485
709
 
486
710
  const response = await this.sendAndWait(Opcode.SESSION_INIT, payload);
@@ -493,6 +717,41 @@ class WebMaxClient extends EventEmitter {
493
717
  return response;
494
718
  }
495
719
 
720
+ /**
721
+ * Обработка уведомлений от Socket транспорта
722
+ */
723
+ async handleSocketNotification(data) {
724
+ try {
725
+ if (this.debug && data.opcode !== Opcode.PING) {
726
+ const payload = data.payload?.error ? ` error=${JSON.stringify(data.payload.error)}` : '';
727
+ console.log(`📥 ${getOpcodeName(data.opcode)} (seq=${data.seq})${payload}`);
728
+ }
729
+
730
+ switch (data.opcode) {
731
+ case Opcode.NOTIF_MESSAGE:
732
+ await this.handleNewMessage(data.payload);
733
+ break;
734
+
735
+ case Opcode.NOTIF_MSG_DELETE:
736
+ await this.handleRemovedMessage(data.payload);
737
+ break;
738
+
739
+ case Opcode.NOTIF_CHAT:
740
+ await this.handleChatAction(data.payload);
741
+ break;
742
+
743
+ case Opcode.PING:
744
+ break;
745
+
746
+ default:
747
+ this.emit('raw_message', data);
748
+ }
749
+ } catch (error) {
750
+ console.error('Ошибка при обработке Socket уведомления:', error);
751
+ await this.triggerHandlers(EventTypes.ERROR, error);
752
+ }
753
+ }
754
+
496
755
  /**
497
756
  * Обработка переподключения
498
757
  */
@@ -510,16 +769,16 @@ class WebMaxClient extends EventEmitter {
510
769
  }
511
770
 
512
771
  /**
513
- * Обработка входящих сообщений
772
+ * Обработка входящих сообщений (WebSocket)
514
773
  */
515
774
  async handleMessage(data) {
516
775
  try {
517
776
  const message = JSON.parse(data.toString());
518
777
 
519
- // Отладочное логирование (раскомментируйте при необходимости)
520
- // if (message.opcode !== Opcode.PING) {
521
- // console.log(`📥 Получено: ${getOpcodeName(message.opcode)} (seq=${message.seq})`);
522
- // }
778
+ if (this.debug && message.opcode !== Opcode.PING) {
779
+ const payload = message.payload?.error ? ` error=${JSON.stringify(message.payload.error)}` : '';
780
+ console.log(`📥 ${getOpcodeName(message.opcode)} (seq=${message.seq})${payload}`);
781
+ }
523
782
 
524
783
  // Обработка ответов на запросы по seq
525
784
  if (message.seq && this.pendingRequests.has(message.seq)) {
@@ -549,7 +808,6 @@ class WebMaxClient extends EventEmitter {
549
808
  break;
550
809
 
551
810
  case Opcode.PING:
552
- // Отвечаем на ping (без логирования)
553
811
  await this.sendPong();
554
812
  break;
555
813
 
@@ -629,21 +887,25 @@ class WebMaxClient extends EventEmitter {
629
887
  }
630
888
 
631
889
  /**
632
- * Отправка запроса через WebSocket и ожидание ответа
890
+ * Отправка запроса и ожидание ответа
633
891
  */
634
- sendAndWait(opcode, payload, cmd = 0, timeout = 20000) {
635
- return new Promise((resolve, reject) => {
636
- if (!this.isConnected) {
637
- reject(new Error('WebSocket не подключен'));
638
- return;
639
- }
892
+ async sendAndWait(opcode, payload, cmd = 0, timeout = 20000) {
893
+ if (!this.isConnected) {
894
+ throw new Error('Соединение не установлено');
895
+ }
640
896
 
897
+ // Используем Socket транспорт для IOS/ANDROID
898
+ if (this._useSocketTransport && this._socketTransport) {
899
+ return await this._socketTransport.sendAndWait(opcode, payload, cmd, timeout);
900
+ }
901
+
902
+ // WebSocket транспорт для WEB
903
+ return new Promise((resolve, reject) => {
641
904
  const message = this.makeMessage(opcode, payload, cmd);
642
905
  const seq = message.seq;
643
906
 
644
907
  this.pendingRequests.set(seq, { resolve, reject });
645
908
 
646
- // Таймаут для запроса
647
909
  const timeoutId = setTimeout(() => {
648
910
  if (this.pendingRequests.has(seq)) {
649
911
  this.pendingRequests.delete(seq);
@@ -651,19 +913,14 @@ class WebMaxClient extends EventEmitter {
651
913
  }
652
914
  }, timeout);
653
915
 
654
- // Сохраняем timeoutId чтобы можно было отменить
655
916
  this.pendingRequests.get(seq).timeoutId = timeoutId;
656
917
 
657
- // Отладочное логирование (раскомментируйте при необходимости)
658
- // if (opcode !== Opcode.PING) {
659
- // console.log(`📤 Отправка: ${getOpcodeName(opcode)} (seq=${seq})`);
660
- // }
661
918
  this.ws.send(JSON.stringify(message));
662
919
  });
663
920
  }
664
921
 
665
922
  /**
666
- * Отправка сообщения
923
+ * Отправка сообщения (с уведомлением)
667
924
  */
668
925
  async sendMessage(options) {
669
926
  if (typeof options === 'string') {
@@ -672,6 +929,37 @@ class WebMaxClient extends EventEmitter {
672
929
 
673
930
  const { chatId, text, cid, replyTo, attachments } = options;
674
931
 
932
+ const payload = {
933
+ chatId: chatId,
934
+ message: {
935
+ text: text || '',
936
+ cid: cid || Date.now(),
937
+ elements: [],
938
+ attaches: attachments || [],
939
+ link: replyTo ? { type: 'REPLY', messageId: replyTo } : null
940
+ },
941
+ notify: true
942
+ };
943
+
944
+ const response = await this.sendAndWait(Opcode.MSG_SEND, payload);
945
+
946
+ if (response.payload && response.payload.message) {
947
+ return new Message(response.payload.message, this);
948
+ }
949
+
950
+ return response.payload;
951
+ }
952
+
953
+ /**
954
+ * Отправка сообщения в канал (без уведомления)
955
+ */
956
+ async sendMessageChannel(options) {
957
+ if (typeof options === 'string') {
958
+ throw new Error('sendMessageChannel требует объект с параметрами: { chatId, text, cid }');
959
+ }
960
+
961
+ const { chatId, text, cid, replyTo, attachments } = options;
962
+
675
963
  const payload = {
676
964
  chatId: chatId,
677
965
  message: {
@@ -769,6 +1057,10 @@ class WebMaxClient extends EventEmitter {
769
1057
  * Получение списка чатов
770
1058
  */
771
1059
  async getChats(marker = 0) {
1060
+ if (this._useSocketTransport && this._socketTransport) {
1061
+ return await this._socketTransport.getChats(marker);
1062
+ }
1063
+
772
1064
  const payload = {
773
1065
  marker: marker
774
1066
  };
@@ -782,6 +1074,11 @@ class WebMaxClient extends EventEmitter {
782
1074
  * Получение истории сообщений
783
1075
  */
784
1076
  async getHistory(chatId, from = Date.now(), backward = 200, forward = 0) {
1077
+ if (this._useSocketTransport && this._socketTransport) {
1078
+ const messages = await this._socketTransport.getHistory(chatId, from, backward, forward);
1079
+ return messages.map(msg => new Message(msg, this));
1080
+ }
1081
+
785
1082
  const payload = {
786
1083
  chatId: chatId,
787
1084
  from: from,
@@ -819,6 +1116,10 @@ class WebMaxClient extends EventEmitter {
819
1116
  * Остановка клиента
820
1117
  */
821
1118
  async stop() {
1119
+ if (this._socketTransport) {
1120
+ await this._socketTransport.close();
1121
+ this._socketTransport = null;
1122
+ }
822
1123
  if (this.ws) {
823
1124
  this.ws.close();
824
1125
  this.ws = null;
package/lib/opcodes.js CHANGED
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * Opcodes для протокола Max API
3
- * Портировано из PyMax
4
3
  */
5
4
 
6
5
  const Opcode = {
@@ -10,6 +9,8 @@ const Opcode = {
10
9
  LOG: 5,
11
10
  SESSION_INIT: 6,
12
11
  PROFILE: 16,
12
+ AUTH_REQUEST: 17,
13
+ AUTH: 18,
13
14
  LOGIN: 19,
14
15
  LOGOUT: 20,
15
16
  SYNC: 21,
@@ -40,6 +41,7 @@ const Opcode = {
40
41
  NOTIF_MESSAGE: 128,
41
42
  NOTIF_CHAT: 135,
42
43
  NOTIF_ATTACH: 136,
44
+ NOTIF_MSG_DELETE: 154,
43
45
  NOTIF_MSG_REACTIONS_CHANGED: 155,
44
46
  MSG_REACTION: 178,
45
47
  MSG_CANCEL_REACTION: 179,