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 +12 -4
- package/api.package.md +2 -0
- package/lib/client.js +126 -20
- package/lib/session.js +16 -0
- package/lib/socketTransport.js +23 -2
- package/package.json +2 -2
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
|
-
|
|
36
|
+
Ответы сервера по TCP содержат полезную нагрузку в **LZ4**-блоках (поверх **msgpack**). Для распаковки используется **`lz4js`** — чистый JavaScript, **без node-gyp** и нативной сборки, в том числе на Windows без Visual Studio C++. Он входит в зависимости `webmaxsocket` и ставится вместе с пакетом. При необходимости можно доустановить вручную:
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
npm install
|
|
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
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
347
|
+
const { token: tokenToUse, source: tokenSource } = this._resolveTokenForStart();
|
|
348
|
+
|
|
287
349
|
if (tokenToUse) {
|
|
288
|
-
if (
|
|
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',
|
|
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
|
|
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 (
|
|
307
|
-
throw new Error(
|
|
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
|
*/
|
package/lib/socketTransport.js
CHANGED
|
@@ -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 =
|
|
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.
|
|
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
|
-
"
|
|
41
|
+
"lz4js": "^0.2.0",
|
|
42
42
|
"qrcode-terminal": "^0.12.0",
|
|
43
43
|
"uuid": "^9.0.0",
|
|
44
44
|
"ws": "^8.18.0"
|