webmaxsocket 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 CHANGED
@@ -31,12 +31,18 @@
31
31
  npm install webmaxsocket
32
32
  ```
33
33
 
34
- ### Зависимости для Socket транспорта (IOS/ANDROID)
34
+ ### Зависимости для Socket транспорта (IOS/ANDROID) / Socket transport dependencies
35
35
 
36
- Для работы с TCP Socket транспортом требуется библиотека `lz4`. Если при установке возникают проблемы с `node-gyp`:
36
+ Ответы сервера по TCP содержат полезную нагрузку в **LZ4**-блоках (поверх **msgpack**). Для распаковки используется **`lz4js`** — чистый JavaScript, **без node-gyp** и нативной сборки, в том числе на Windows без Visual Studio C++. Он входит в зависимости `webmaxsocket` и ставится вместе с пакетом. При необходимости можно доустановить вручную:
37
37
 
38
38
  ```bash
39
- npm install lz4 --ignore-scripts
39
+ npm install lz4js
40
+ ```
41
+
42
+ Дополнительно можно установить нативный модуль **`lz4`**, если в окружении доступна сборка C++:
43
+
44
+ ```bash
45
+ npm install lz4
40
46
  ```
41
47
 
42
48
  **Примечание:** Для обычной QR-авторизации (WEB) дополнительные зависимости не нужны. Socket транспорт используется только после сохранения сессии или при явном указании `deviceType: 'IOS'`/`'ANDROID'`.
@@ -668,7 +674,7 @@ ChatActions.RECORDING_VIDEO // Записывает видео
668
674
 
669
675
  ### MaxSocketTransport
670
676
 
671
- Низкоуровневый TCP Socket транспорт для IOS/ANDROID (api.oneme.ru).
677
+ Низкоуровневый TCP Socket транспорт для IOS/ANDROID (api.oneme.ru). Входящие пакеты с флагом сжатия распаковываются через **LZ4** (см. раздел **«Зависимости для Socket транспорта»** выше).
672
678
 
673
679
  #### Прямое использование (advanced)
674
680
 
@@ -827,6 +833,8 @@ DEBUG=1 node example.js
827
833
 
828
834
  7. **TCP и keep-alive (PING):** сервер периодически шлёт `PING`. На WebSocket клиент отвечает `sendPong`; на **TCP** раньше ответ не отправлялся — соединение могло обрываться через несколько минут, после чего процесс Node **завершался** (нечем держать event loop). Сейчас на TCP автоматически шлётся тот же ответ, что и у WebSocket (`PING` с пустым payload).
829
835
 
836
+ 8. **LZ4:** для IOS/ANDROID входящие данные распаковываются из LZ4-блоков; **`lz4js`** входит в зависимости пакета. При необходимости можно установить нативный **`lz4`** (см. раздел **«Зависимости для Socket транспорта»**).
837
+
830
838
  ## 🔗 Ссылки / Links
831
839
 
832
840
  - [GitHub Repository](https://github.com/Tellarion/webmaxsocket)
package/api.package.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Краткий перечень возможностей для работы с библиотекой. Подробности и примеры — в `README.md`.
4
4
 
5
+ **Зависимости TCP (IOS/ANDROID):** ответы по сокету сжаты **LZ4**; для распаковки используется **`lz4js`**. См. `README.md` → «Зависимости для Socket транспорта».
6
+
5
7
  ---
6
8
 
7
9
  ## Экспорт пакета (`require('webmaxsocket')`)
package/lib/client.js CHANGED
@@ -68,17 +68,28 @@ class WebMaxClient extends EventEmitter {
68
68
  this.apiUrl = options.apiUrl || 'wss://ws-api.oneme.ru/websocket';
69
69
 
70
70
  // Загрузка из config — token, ua (agent), device_type
71
- let token = options.token || null;
72
71
  let agent = options.ua || options.agent || options.headerUserAgent || null;
73
72
  let configObj = {};
74
73
  const configPath = options.configPath || options.config;
75
74
  if (configPath) {
76
75
  configObj = loadSessionConfig(configPath);
77
- token = token || configObj.token || null;
78
76
  agent = agent || configObj.agent || configObj.ua || configObj.headerUserAgent || null;
79
77
  }
80
-
81
- this._providedToken = token;
78
+
79
+ const optTok = options.token;
80
+ this._explicitOptionToken =
81
+ optTok !== undefined && optTok !== null && String(optTok).trim() !== ''
82
+ ? String(optTok).trim()
83
+ : null;
84
+ this._configFileToken =
85
+ configPath &&
86
+ configObj.token !== undefined &&
87
+ configObj.token !== null &&
88
+ String(configObj.token).trim() !== ''
89
+ ? String(configObj.token).trim()
90
+ : null;
91
+ /** Сырые токены из опций/config (без sessions); приоритет при старте см. _resolveTokenForStart */
92
+ this._providedToken = this._explicitOptionToken || this._configFileToken;
82
93
  this._saveTokenToSession = options.saveToken !== false;
83
94
  this.origin = options.origin || 'https://web.max.ru';
84
95
  /** Доп. заголовок для ws (например Referer при QR с max.ru, пока Origin остаётся web.max.ru — иначе 403). */
@@ -166,6 +177,9 @@ class WebMaxClient extends EventEmitter {
166
177
  Boolean(options.debug) ||
167
178
  process.env.DEBUG === '1' ||
168
179
  process.env.WEBMAX_DEBUG === '1';
180
+ this._authDebug = Boolean(options.authDebug) || process.env.WEBMAX_AUTH_DEBUG === '1';
181
+ /** После успешного sync копировать sessions/<name>.json → <name>.last_ok.json */
182
+ this._sessionBackupOnSuccess = options.sessionBackup !== false;
169
183
  /** Режим JSON-лога входящих: off | messages | verbose (см. logIncoming в README) */
170
184
  this._incomingLogMode = resolveIncomingLogMode(options);
171
185
  this._wireIncomingLogListeners();
@@ -200,6 +214,55 @@ class WebMaxClient extends EventEmitter {
200
214
  });
201
215
  }
202
216
 
217
+ /**
218
+ * Приоритет токена: явный options.token → сохранённая sessions/<name>.json → token из config.
219
+ * Иначе после SMS в sessions лежит новый токен, а из config подставлялся старый и sync падал с «обновите config».
220
+ */
221
+ _resolveTokenForStart() {
222
+ if (this._explicitOptionToken) {
223
+ return { token: this._explicitOptionToken, source: 'options.token' };
224
+ }
225
+ const sessionTok = this.session.get('token');
226
+ if (sessionTok && String(sessionTok).trim() !== '') {
227
+ return { token: String(sessionTok).trim(), source: 'session_file' };
228
+ }
229
+ if (this._configFileToken) {
230
+ return { token: this._configFileToken, source: 'config_file' };
231
+ }
232
+ return { token: null, source: null };
233
+ }
234
+
235
+ _logAuthContext(phase, extra = {}) {
236
+ if (!this.debug && !this._authDebug) return;
237
+ let libVersion = 'unknown';
238
+ try {
239
+ libVersion = require(path.join(__dirname, '..', 'package.json')).version;
240
+ } catch (_) {
241
+ /* ignore */
242
+ }
243
+ const tok = this._token || this.session.get('token');
244
+ const tokenPreview =
245
+ tok && String(tok).length > 14
246
+ ? `${String(tok).slice(0, 8)}…${String(tok).slice(-4)}`
247
+ : tok
248
+ ? '(short)'
249
+ : '(none)';
250
+ const payload = {
251
+ phase,
252
+ libVersion,
253
+ node: process.version,
254
+ cwd: process.cwd(),
255
+ sessionName: this.sessionName,
256
+ sessionPath: this.session.sessionFile,
257
+ backupPath: path.join(this.session.sessionDir, `${this.sessionName}.last_ok.json`),
258
+ tokenPreview,
259
+ deviceId: this.deviceId,
260
+ userAgent: this.userAgent.toJSON(),
261
+ ...extra
262
+ };
263
+ console.log('[webmaxsocket:auth]', JSON.stringify(payload, null, 2));
264
+ }
265
+
203
266
  /**
204
267
  * Регистрация обработчика события start
205
268
  */
@@ -281,32 +344,47 @@ class WebMaxClient extends EventEmitter {
281
344
  // Подключаемся к WebSocket или Socket
282
345
  await this.connect();
283
346
 
284
- // Приоритет: 1) переданный токен, 2) сохранённая сессия, 3) QR-авторизация
285
- const tokenToUse = this._providedToken || this.session.get('token');
286
-
347
+ const { token: tokenToUse, source: tokenSource } = this._resolveTokenForStart();
348
+
287
349
  if (tokenToUse) {
288
- if (this._providedToken) {
350
+ if (tokenSource === 'session_file') {
351
+ console.log('✅ Найдена сохраненная сессия');
352
+ } else {
289
353
  console.log('✅ Вход по токену (token auth)');
290
354
  if (this._saveTokenToSession) {
291
- this.session.set('token', this._providedToken);
355
+ this.session.set('token', tokenToUse);
292
356
  this.session.set('deviceId', this.deviceId);
293
357
  }
294
- } else {
295
- console.log('✅ Найдена сохраненная сессия');
296
358
  }
297
359
  this._token = tokenToUse;
298
-
360
+
361
+ this._logAuthContext('start:before_sync', { tokenSource });
362
+
299
363
  try {
300
364
  await this.sync();
301
365
  this.isAuthorized = true;
302
366
  } catch (error) {
303
- const wasTokenAuth = !!this._providedToken;
367
+ const fromConfigOrOptions =
368
+ tokenSource === 'config_file' || tokenSource === 'options.token';
304
369
  this.session.clear();
370
+ this._explicitOptionToken = null;
371
+ this._configFileToken = null;
305
372
  this._providedToken = null;
306
- if (wasTokenAuth) {
307
- throw new Error(`Токен недействителен или сессия истекла. Обновите токен в config. (${error.message})`);
373
+ if (fromConfigOrOptions) {
374
+ throw new Error(
375
+ `Токен недействителен или сессия истекла. Обновите token в config или удалите/очистите token в config, чтобы использовалась актуальная сессия из sessions/. (${error.message})`
376
+ );
308
377
  }
309
378
  console.log('⚠️ Сессия истекла, требуется повторная авторизация');
379
+ if (this.debug || this._authDebug) {
380
+ console.log(
381
+ '[webmaxsocket:auth] Подсказка: для восстановления попробуйте скопировать sessions/' +
382
+ this.sessionName +
383
+ '.last_ok.json поверх sessions/' +
384
+ this.sessionName +
385
+ '.json (если копия есть).'
386
+ );
387
+ }
310
388
  await this.authorize();
311
389
  }
312
390
  } else {
@@ -1102,10 +1180,11 @@ class WebMaxClient extends EventEmitter {
1102
1180
  this._token = token;
1103
1181
 
1104
1182
  console.log('✅ Авторизация по SMS успешна!');
1105
-
1183
+ this._logAuthContext('sms:after_login', { tokenSource: 'sms' });
1184
+
1106
1185
  // Выполняем sync
1107
1186
  await this.sync();
1108
-
1187
+
1109
1188
  return token;
1110
1189
  }
1111
1190
  };
@@ -1139,7 +1218,9 @@ class WebMaxClient extends EventEmitter {
1139
1218
  */
1140
1219
  async sync() {
1141
1220
  console.log('🔄 Синхронизация с сервером...');
1142
-
1221
+
1222
+ this._logAuthContext('sync');
1223
+
1143
1224
  const token = this._token || this.session.get('token');
1144
1225
 
1145
1226
  if (!token) {
@@ -1189,7 +1270,27 @@ class WebMaxClient extends EventEmitter {
1189
1270
  } else {
1190
1271
  console.log('⚠️ Данные пользователя не найдены в ответе sync');
1191
1272
  }
1192
-
1273
+
1274
+ try {
1275
+ let libVersion = 'unknown';
1276
+ try {
1277
+ libVersion = require(path.join(__dirname, '..', 'package.json')).version;
1278
+ } catch (_) {
1279
+ /* ignore */
1280
+ }
1281
+ this.session.set('_authMeta', {
1282
+ lastSyncOkAt: new Date().toISOString(),
1283
+ webmaxsocket: libVersion,
1284
+ node: process.version
1285
+ });
1286
+ } catch (_) {
1287
+ /* ignore */
1288
+ }
1289
+ if (this._sessionBackupOnSuccess) {
1290
+ this.session.copyToLastOk();
1291
+ }
1292
+ this._logAuthContext('sync:ok', { userId: this.me?.id });
1293
+
1193
1294
  return responsePayload;
1194
1295
  }
1195
1296
 
@@ -1227,13 +1328,18 @@ class WebMaxClient extends EventEmitter {
1227
1328
  }
1228
1329
 
1229
1330
  this._token = token;
1230
-
1331
+
1332
+ this._logAuthContext('connectWithSession:before_sync', { tokenSource: 'session_file' });
1333
+
1231
1334
  try {
1232
1335
  await this.sync();
1233
1336
  this.isAuthorized = true;
1234
1337
  console.log('Подключение с сохраненной сессией успешно');
1235
1338
  } catch (error) {
1236
1339
  console.log('Сессия истекла, требуется повторная авторизация');
1340
+ if (this.debug || this._authDebug) {
1341
+ console.log('[webmaxsocket:auth] sync error:', error.message);
1342
+ }
1237
1343
  this.session.clear();
1238
1344
  await this.authorize();
1239
1345
  }
package/lib/session.js CHANGED
@@ -40,6 +40,22 @@ class SessionManager {
40
40
  return false;
41
41
  }
42
42
 
43
+ /**
44
+ * Копия последнего успешного состояния (после удачного sync) — для ручного восстановления:
45
+ * скопируйте `.last_ok.json` на место основного файла сессии.
46
+ */
47
+ copyToLastOk() {
48
+ try {
49
+ if (!fs.existsSync(this.sessionFile)) return false;
50
+ const okPath = path.join(this.sessionDir, `${this.sessionName}.last_ok.json`);
51
+ fs.copyFileSync(this.sessionFile, okPath);
52
+ return true;
53
+ } catch (error) {
54
+ console.error('Ошибка резервной копии сессии:', error.message);
55
+ return false;
56
+ }
57
+ }
58
+
43
59
  /**
44
60
  * Сохраняет данные сессии в файл
45
61
  */
@@ -6,8 +6,29 @@
6
6
 
7
7
  const tls = require('tls');
8
8
  const { encode: msgpackEncode, decode: msgpackDecode } = require('@msgpack/msgpack');
9
- const lz4Binding = require('lz4/lib/binding.js');
10
9
  const { v4: uuidv4 } = require('uuid');
10
+
11
+ /**
12
+ * Распаковка LZ4-блока (как в протоколе Max).
13
+ * Сначала нативный `lz4` (нужен node-gyp / VS C++ на Windows), иначе чистый JS `lz4js`.
14
+ */
15
+ function resolveLz4Uncompress() {
16
+ try {
17
+ const lz4Binding = require('lz4/lib/binding.js');
18
+ return (inp, out) => lz4Binding.uncompress(inp, out);
19
+ } catch (_) {
20
+ try {
21
+ const lz4js = require('lz4js');
22
+ return (inp, out) => lz4js.decompressBlock(inp, out, 0, inp.length, 0);
23
+ } catch (e) {
24
+ throw new Error(
25
+ 'LZ4: установите зависимость `lz4js` (npm i lz4js) или соберите нативный `lz4` (Visual Studio, C++ workload). ' +
26
+ e.message
27
+ );
28
+ }
29
+ }
30
+ }
31
+ const lz4Uncompress = resolveLz4Uncompress();
11
32
  const { Opcode, getOpcodeName } = require('./opcodes');
12
33
  const { UserAgentPayload } = require('./userAgent');
13
34
 
@@ -50,7 +71,7 @@ function unpackPacket(data) {
50
71
  try {
51
72
  if (compFlag !== 0) {
52
73
  const out = Buffer.alloc(Math.max(payloadLength * 20, 256 * 1024));
53
- const n = lz4Binding.uncompress(payloadBytes, out);
74
+ const n = lz4Uncompress(payloadBytes, out);
54
75
  if (n > 0) payload = msgpackDecode(out.subarray(0, n));
55
76
  } else {
56
77
  payload = msgpackDecode(payloadBytes);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webmaxsocket",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
4
4
  "description": "Node.js client for Max Messenger with QR code and token authentication",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -38,7 +38,7 @@
38
38
  "homepage": "https://github.com/Tellarion/webmaxsocket#readme",
39
39
  "dependencies": {
40
40
  "@msgpack/msgpack": "^3.1.3",
41
- "lz4": "^0.6.5",
41
+ "lz4js": "^0.2.0",
42
42
  "qrcode-terminal": "^0.12.0",
43
43
  "uuid": "^9.0.0",
44
44
  "ws": "^8.18.0"