refactor: improved logging, moved to custom logger instead of debug
This commit is contained in:
parent
64aae43572
commit
9b5ca0cb2a
27 changed files with 495 additions and 141 deletions
|
@ -18,7 +18,6 @@
|
||||||
"@mtcute/file-id": "^1.0.0",
|
"@mtcute/file-id": "^1.0.0",
|
||||||
"eager-async-pool": "^1.0.0",
|
"eager-async-pool": "^1.0.0",
|
||||||
"file-type": "^16.2.0",
|
"file-type": "^16.2.0",
|
||||||
"big-integer": "1.6.48",
|
"big-integer": "1.6.48"
|
||||||
"debug": "^4.3.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -228,6 +228,7 @@ import {
|
||||||
TelegramConnection,
|
TelegramConnection,
|
||||||
} from '@mtcute/core'
|
} from '@mtcute/core'
|
||||||
import { tdFileId } from '@mtcute/file-id'
|
import { tdFileId } from '@mtcute/file-id'
|
||||||
|
import { Logger } from '@mtcute/core/src/utils/logger'
|
||||||
|
|
||||||
export interface TelegramClient extends BaseTelegramClient {
|
export interface TelegramClient extends BaseTelegramClient {
|
||||||
/**
|
/**
|
||||||
|
@ -3499,6 +3500,7 @@ export class TelegramClient extends BaseTelegramClient {
|
||||||
protected _catchUpChannels?: boolean
|
protected _catchUpChannels?: boolean
|
||||||
protected _cpts: Record<number, number>
|
protected _cpts: Record<number, number>
|
||||||
protected _cptsMod: Record<number, number>
|
protected _cptsMod: Record<number, number>
|
||||||
|
protected _updsLog: Logger
|
||||||
constructor(opts: BaseTelegramClient.Options) {
|
constructor(opts: BaseTelegramClient.Options) {
|
||||||
super(opts)
|
super(opts)
|
||||||
this._userId = null
|
this._userId = null
|
||||||
|
@ -3522,6 +3524,8 @@ export class TelegramClient extends BaseTelegramClient {
|
||||||
this._cptsMod = {}
|
this._cptsMod = {}
|
||||||
|
|
||||||
this._selfChanged = false
|
this._selfChanged = false
|
||||||
|
|
||||||
|
this._updsLog = this.log.create('updates')
|
||||||
}
|
}
|
||||||
|
|
||||||
acceptTos = acceptTos
|
acceptTos = acceptTos
|
||||||
|
|
|
@ -66,3 +66,6 @@ import {
|
||||||
|
|
||||||
// @copy
|
// @copy
|
||||||
import { tdFileId } from '@mtcute/file-id'
|
import { tdFileId } from '@mtcute/file-id'
|
||||||
|
|
||||||
|
// @copy
|
||||||
|
import { Logger } from '@mtcute/core/src/utils/logger'
|
||||||
|
|
|
@ -20,7 +20,6 @@ try {
|
||||||
path = require('path')
|
path = require('path')
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
const debug = require('debug')('mtcute:upload')
|
|
||||||
|
|
||||||
const OVERRIDE_MIME: Record<string, string> = {
|
const OVERRIDE_MIME: Record<string, string> = {
|
||||||
// tg doesn't interpret `audio/opus` files as voice messages for some reason
|
// tg doesn't interpret `audio/opus` files as voice messages for some reason
|
||||||
|
@ -201,7 +200,7 @@ export async function uploadFile(
|
||||||
const hash = this._crypto.createMd5()
|
const hash = this._crypto.createMd5()
|
||||||
|
|
||||||
const partCount = ~~((fileSize + partSize - 1) / partSize)
|
const partCount = ~~((fileSize + partSize - 1) / partSize)
|
||||||
debug(
|
this._baseLog.debug(
|
||||||
'uploading %d bytes file in %d chunks, each %d bytes',
|
'uploading %d bytes file in %d chunks, each %d bytes',
|
||||||
fileSize,
|
fileSize,
|
||||||
partCount,
|
partCount,
|
||||||
|
|
|
@ -10,13 +10,12 @@ import {
|
||||||
getBarePeerId,
|
getBarePeerId,
|
||||||
getMarkedPeerId,
|
getMarkedPeerId,
|
||||||
markedPeerIdToBare,
|
markedPeerIdToBare,
|
||||||
MAX_CHANNEL_ID, RpcError,
|
MAX_CHANNEL_ID,
|
||||||
} from '@mtcute/core'
|
} from '@mtcute/core'
|
||||||
import { isDummyUpdate, isDummyUpdates } from '../utils/updates-utils'
|
import { isDummyUpdate } from '../utils/updates-utils'
|
||||||
import { ChatsIndex, UsersIndex } from '../types'
|
import { ChatsIndex, UsersIndex } from '../types'
|
||||||
import { _parseUpdate } from '../utils/parse-update'
|
import { _parseUpdate } from '../utils/parse-update'
|
||||||
|
import { Logger } from '@mtcute/core/src/utils/logger'
|
||||||
const debug = require('debug')('mtcute:upds')
|
|
||||||
|
|
||||||
// code in this file is very bad, thanks to Telegram's awesome updates mechanism
|
// code in this file is very bad, thanks to Telegram's awesome updates mechanism
|
||||||
|
|
||||||
|
@ -46,6 +45,8 @@ interface UpdatesState {
|
||||||
|
|
||||||
_cpts: Record<number, number>
|
_cpts: Record<number, number>
|
||||||
_cptsMod: Record<number, number>
|
_cptsMod: Record<number, number>
|
||||||
|
|
||||||
|
_updsLog: Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// @initialize
|
// @initialize
|
||||||
|
@ -62,6 +63,8 @@ function _initializeUpdates(this: TelegramClient) {
|
||||||
this._cptsMod = {}
|
this._cptsMod = {}
|
||||||
|
|
||||||
this._selfChanged = false
|
this._selfChanged = false
|
||||||
|
|
||||||
|
this._updsLog = this.log.create('updates')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -103,7 +106,7 @@ export async function _fetchUpdatesState(this: TelegramClient): Promise<void> {
|
||||||
this._date = state.date
|
this._date = state.date
|
||||||
this._seq = state.seq
|
this._seq = state.seq
|
||||||
|
|
||||||
debug(
|
this._updsLog.debug(
|
||||||
'loaded initial state: pts=%d, qts=%d, date=%d, seq=%d',
|
'loaded initial state: pts=%d, qts=%d, date=%d, seq=%d',
|
||||||
state.pts,
|
state.pts,
|
||||||
state.qts,
|
state.qts,
|
||||||
|
@ -463,9 +466,15 @@ async function _loadDifference(
|
||||||
|
|
||||||
switch (diff._) {
|
switch (diff._) {
|
||||||
case 'updates.differenceEmpty':
|
case 'updates.differenceEmpty':
|
||||||
|
this._updsLog.debug(
|
||||||
|
'updates.getDifference returned updates.differenceEmpty'
|
||||||
|
)
|
||||||
return
|
return
|
||||||
case 'updates.differenceTooLong':
|
case 'updates.differenceTooLong':
|
||||||
this._pts = diff.pts
|
this._pts = diff.pts
|
||||||
|
this._updsLog.debug(
|
||||||
|
'updates.getDifference returned updates.differenceTooLong'
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -474,6 +483,16 @@ async function _loadDifference(
|
||||||
? diff.state
|
? diff.state
|
||||||
: diff.intermediateState
|
: diff.intermediateState
|
||||||
|
|
||||||
|
this._updsLog.debug(
|
||||||
|
'updates.getDifference returned %d messages, %d updates. new pts: %d, qts: %d, seq: %d, final: %b',
|
||||||
|
diff.newMessages.length,
|
||||||
|
diff.otherUpdates.length,
|
||||||
|
state.pts,
|
||||||
|
state.qts,
|
||||||
|
state.seq,
|
||||||
|
diff._ === 'updates.difference'
|
||||||
|
)
|
||||||
|
|
||||||
await this._cachePeersFrom(diff)
|
await this._cachePeersFrom(diff)
|
||||||
|
|
||||||
const { users, chats } = createUsersChatsIndex(diff)
|
const { users, chats } = createUsersChatsIndex(diff)
|
||||||
|
@ -524,8 +543,23 @@ async function _loadDifference(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextLocalPts) {
|
if (nextLocalPts) {
|
||||||
if (nextLocalPts > pts) continue
|
if (nextLocalPts > pts) {
|
||||||
|
this._updsLog.debug(
|
||||||
|
'ignoring %s (in channel %d) because already handled (by pts: exp %d, got %d)',
|
||||||
|
upd._,
|
||||||
|
cid,
|
||||||
|
nextLocalPts,
|
||||||
|
pts
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
if (nextLocalPts < pts) {
|
if (nextLocalPts < pts) {
|
||||||
|
this._updsLog.debug(
|
||||||
|
'fetching channel %d difference because gap detected (by pts: exp %d, got %d)',
|
||||||
|
cid,
|
||||||
|
nextLocalPts,
|
||||||
|
pts
|
||||||
|
)
|
||||||
await _loadChannelDifference.call(
|
await _loadChannelDifference.call(
|
||||||
this,
|
this,
|
||||||
cid,
|
cid,
|
||||||
|
@ -550,6 +584,7 @@ async function _loadDifference(
|
||||||
|
|
||||||
this._pts = state.pts
|
this._pts = state.pts
|
||||||
this._qts = state.qts
|
this._qts = state.qts
|
||||||
|
this._seq = state.seq
|
||||||
this._date = state.date
|
this._date = state.date
|
||||||
|
|
||||||
if (diff._ === 'updates.difference') return
|
if (diff._ === 'updates.difference') return
|
||||||
|
@ -568,6 +603,10 @@ async function _loadChannelDifference(
|
||||||
await this.resolvePeer(MAX_CHANNEL_ID - channelId)
|
await this.resolvePeer(MAX_CHANNEL_ID - channelId)
|
||||||
)!
|
)!
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
this._updsLog.warn(
|
||||||
|
'getChannelDifference failed for channel %d: input peer not found',
|
||||||
|
channelId
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -591,7 +630,13 @@ async function _loadChannelDifference(
|
||||||
filter: { _: 'channelMessagesFilterEmpty' },
|
filter: { _: 'channelMessagesFilterEmpty' },
|
||||||
})
|
})
|
||||||
|
|
||||||
if (diff._ === 'updates.channelDifferenceEmpty') break
|
if (diff._ === 'updates.channelDifferenceEmpty') {
|
||||||
|
this._updsLog.debug(
|
||||||
|
'getChannelDifference (cid = %d) returned channelDifferenceEmpty',
|
||||||
|
channelId
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
await this._cachePeersFrom(diff)
|
await this._cachePeersFrom(diff)
|
||||||
|
|
||||||
|
@ -602,6 +647,13 @@ async function _loadChannelDifference(
|
||||||
pts = diff.dialog.pts!
|
pts = diff.dialog.pts!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._updsLog.warn(
|
||||||
|
'getChannelDifference (cid = %d) returned channelDifferenceTooLong. new pts: %d, recent msgs: %d',
|
||||||
|
channelId,
|
||||||
|
pts,
|
||||||
|
diff.messages.length
|
||||||
|
)
|
||||||
|
|
||||||
diff.messages.forEach((message) => {
|
diff.messages.forEach((message) => {
|
||||||
if (noDispatch && noDispatch.msg[channelId]?.[message.id])
|
if (noDispatch && noDispatch.msg[channelId]?.[message.id])
|
||||||
return
|
return
|
||||||
|
@ -612,6 +664,15 @@ async function _loadChannelDifference(
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._updsLog.warn(
|
||||||
|
'getChannelDifference (cid = %d) returned %d messages, %d updates. new pts: %d, final: %b',
|
||||||
|
channelId,
|
||||||
|
diff.newMessages.length,
|
||||||
|
diff.otherUpdates.length,
|
||||||
|
pts,
|
||||||
|
diff.final
|
||||||
|
)
|
||||||
|
|
||||||
diff.newMessages.forEach((message) => {
|
diff.newMessages.forEach((message) => {
|
||||||
if (noDispatch && noDispatch.msg[channelId]?.[message.id]) return
|
if (noDispatch && noDispatch.msg[channelId]?.[message.id]) return
|
||||||
if (message._ === 'messageEmpty') return
|
if (message._ === 'messageEmpty') return
|
||||||
|
@ -682,10 +743,24 @@ async function _processSingleUpdate(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextLocalPts) {
|
if (nextLocalPts) {
|
||||||
if (nextLocalPts > pts)
|
if (nextLocalPts > pts) {
|
||||||
// "the update was already applied, and must be ignored"
|
// "the update was already applied, and must be ignored"
|
||||||
|
this._updsLog.debug(
|
||||||
|
'ignoring %s (cid = %d) because already applied (by pts: exp %d, got %d)',
|
||||||
|
upd._,
|
||||||
|
channelId,
|
||||||
|
nextLocalPts,
|
||||||
|
pts
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
}
|
||||||
if (nextLocalPts < pts) {
|
if (nextLocalPts < pts) {
|
||||||
|
this._updsLog.debug(
|
||||||
|
'fetching difference (cid = %d) because gap detected (by pts: exp %d, got %d)',
|
||||||
|
channelId,
|
||||||
|
nextLocalPts,
|
||||||
|
pts
|
||||||
|
)
|
||||||
if (channelId) {
|
if (channelId) {
|
||||||
// "there's an update gap that must be filled"
|
// "there's an update gap that must be filled"
|
||||||
await _loadChannelDifference.call(
|
await _loadChannelDifference.call(
|
||||||
|
@ -709,18 +784,40 @@ async function _processSingleUpdate(
|
||||||
// qts is only used for non-channel updates
|
// qts is only used for non-channel updates
|
||||||
const nextLocalQts = this._qts! + 1
|
const nextLocalQts = this._qts! + 1
|
||||||
|
|
||||||
if (nextLocalQts > qts)
|
if (nextLocalQts > qts) {
|
||||||
// "the update was already applied, and must be ignored"
|
// "the update was already applied, and must be ignored"
|
||||||
|
this._updsLog.debug(
|
||||||
|
'ignoring %s because already applied (by qts: exp %d, got %d)',
|
||||||
|
upd._,
|
||||||
|
nextLocalQts,
|
||||||
|
qts
|
||||||
|
)
|
||||||
return
|
return
|
||||||
if (nextLocalQts < qts)
|
}
|
||||||
|
if (nextLocalQts < qts) {
|
||||||
|
this._updsLog.debug(
|
||||||
|
'fetching difference because gap detected (by qts: exp %d, got %d)',
|
||||||
|
upd._,
|
||||||
|
nextLocalQts,
|
||||||
|
qts
|
||||||
|
)
|
||||||
|
|
||||||
return await _loadDifference.call(
|
return await _loadDifference.call(
|
||||||
this,
|
this,
|
||||||
noDispatch ? _createNoDispatchIndex(upd) : undefined
|
noDispatch ? _createNoDispatchIndex(upd) : undefined
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// update local pts/qts
|
// update local pts/qts
|
||||||
if (pts) {
|
if (pts) {
|
||||||
|
this._updsLog.debug(
|
||||||
|
'received pts-ordered %s (cid = %d), new pts: %d',
|
||||||
|
upd._,
|
||||||
|
channelId,
|
||||||
|
pts
|
||||||
|
)
|
||||||
|
|
||||||
if (channelId) {
|
if (channelId) {
|
||||||
this._cpts[channelId] = pts
|
this._cpts[channelId] = pts
|
||||||
this._cptsMod[channelId] = pts
|
this._cptsMod[channelId] = pts
|
||||||
|
@ -730,6 +827,8 @@ async function _processSingleUpdate(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (qts) {
|
if (qts) {
|
||||||
|
this._updsLog.debug('received qts-ordered %s, new qts: %d', upd._, qts)
|
||||||
|
|
||||||
this._qts = qts
|
this._qts = qts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -765,6 +864,12 @@ async function _processSingleUpdate(
|
||||||
// this is a short update, let's fetch cached peers
|
// this is a short update, let's fetch cached peers
|
||||||
peers = await _fetchPeersForShort.call(this, upd)
|
peers = await _fetchPeersForShort.call(this, upd)
|
||||||
if (!peers) {
|
if (!peers) {
|
||||||
|
this._updsLog.debug(
|
||||||
|
'fetching difference because some peers were not available for short %s (pts = %d, cid = %d)',
|
||||||
|
upd._,
|
||||||
|
pts,
|
||||||
|
channelId
|
||||||
|
)
|
||||||
// some peer is not cached.
|
// some peer is not cached.
|
||||||
// need to re-fetch the thing, and cache them on the way
|
// need to re-fetch the thing, and cache them on the way
|
||||||
return await _loadDifference.call(this)
|
return await _loadDifference.call(this)
|
||||||
|
@ -785,7 +890,9 @@ export function _handleUpdate(
|
||||||
): void {
|
): void {
|
||||||
// just in case, check that updates state is available
|
// just in case, check that updates state is available
|
||||||
if (this._pts === undefined) {
|
if (this._pts === undefined) {
|
||||||
debug('received an update before updates state is available')
|
this._updsLog.warn(
|
||||||
|
'received an update before updates state is available'
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -800,8 +907,6 @@ export function _handleUpdate(
|
||||||
this._updLock
|
this._updLock
|
||||||
.acquire()
|
.acquire()
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
debug('received %s', update._)
|
|
||||||
|
|
||||||
// i tried my best to follow the documentation, but i still may have missed something.
|
// i tried my best to follow the documentation, but i still may have missed something.
|
||||||
// feel free to contribute!
|
// feel free to contribute!
|
||||||
// reference: https://core.telegram.org/api/updates
|
// reference: https://core.telegram.org/api/updates
|
||||||
|
@ -824,26 +929,41 @@ export function _handleUpdate(
|
||||||
// https://t.me/tdlibchat/5843
|
// https://t.me/tdlibchat/5843
|
||||||
const nextLocalSeq = this._seq! + 1
|
const nextLocalSeq = this._seq! + 1
|
||||||
|
|
||||||
debug(
|
this._updsLog.debug(
|
||||||
'received %s (seq_start=%d, seq_end=%d)',
|
'received %s (seq_start = %d, seq_end = %d)',
|
||||||
update._,
|
update._,
|
||||||
seqStart,
|
seqStart,
|
||||||
update.seq
|
update.seq
|
||||||
)
|
)
|
||||||
|
|
||||||
if (nextLocalSeq > seqStart)
|
if (nextLocalSeq > seqStart) {
|
||||||
|
this._updsLog.debug(
|
||||||
|
'ignoring updates group because already applied (by seq: exp %d, got %d)',
|
||||||
|
nextLocalSeq,
|
||||||
|
seqStart
|
||||||
|
)
|
||||||
// "the updates were already applied, and must be ignored"
|
// "the updates were already applied, and must be ignored"
|
||||||
return
|
return
|
||||||
if (nextLocalSeq < seqStart)
|
}
|
||||||
|
if (nextLocalSeq < seqStart) {
|
||||||
|
this._updsLog.debug(
|
||||||
|
'fetching difference because gap detected (by seq: exp %d, got %d)',
|
||||||
|
nextLocalSeq,
|
||||||
|
seqStart
|
||||||
|
)
|
||||||
// "there's an updates gap that must be filled"
|
// "there's an updates gap that must be filled"
|
||||||
// loading difference will also load any updates contained
|
// loading difference will also load any updates contained
|
||||||
// in this update, so we discard it
|
// in this update, so we discard it
|
||||||
return await _loadDifference.call(this)
|
return await _loadDifference.call(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasMin = await this._cachePeersFrom(update)
|
const hasMin = await this._cachePeersFrom(update)
|
||||||
if (hasMin) {
|
if (hasMin) {
|
||||||
if (!(await _replaceMinPeers.call(this, update))) {
|
if (!(await _replaceMinPeers.call(this, update))) {
|
||||||
|
this._updsLog.debug(
|
||||||
|
'fetching difference because some peers were min and not cached'
|
||||||
|
)
|
||||||
// some min peer is not cached.
|
// some min peer is not cached.
|
||||||
// need to re-fetch the thing, and cache them on the way
|
// need to re-fetch the thing, and cache them on the way
|
||||||
return await _loadDifference.call(this)
|
return await _loadDifference.call(this)
|
||||||
|
@ -1005,6 +1125,6 @@ export function catchUp(this: TelegramClient): Promise<void> {
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export function _keepAliveAction(this: TelegramClient): void {
|
export function _keepAliveAction(this: TelegramClient): void {
|
||||||
debug('no updates for >15 minutes, catching up')
|
this._updsLog.debug('no updates for >15 minutes, catching up')
|
||||||
this.catchUp().catch((err) => this._emitError(err))
|
this.catchUp().catch((err) => this._emitError(err))
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
"browser": {
|
"browser": {
|
||||||
"./utils/platform/crypto.js": "./utils/platform/crypto.web.js",
|
"./utils/platform/crypto.js": "./utils/platform/crypto.web.js",
|
||||||
"./utils/platform/transport.js": "./utils/platform/transport.web.js",
|
"./utils/platform/transport.js": "./utils/platform/transport.web.js",
|
||||||
|
"./utils/platform/logging.js": "./utils/platform/logging.web.js",
|
||||||
"./storage/json-file.js": false
|
"./storage/json-file.js": false
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -23,7 +24,6 @@
|
||||||
"leemon": "6.2.0",
|
"leemon": "6.2.0",
|
||||||
"pako": "2.0.2",
|
"pako": "2.0.2",
|
||||||
"big-integer": "1.6.48",
|
"big-integer": "1.6.48",
|
||||||
"debug": "^4.3.1",
|
|
||||||
"events": "3.2.0"
|
"events": "3.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { BaseTelegramClient, defaultDcs } from '../src'
|
import { BaseTelegramClient, defaultDcs } from '../src'
|
||||||
|
|
||||||
require('dotenv-flow').config({ path: __dirname + '/../' })
|
require('dotenv-flow').config({ path: __dirname + '/../' })
|
||||||
require('debug').enable('mtcute:*')
|
|
||||||
|
|
||||||
if (!process.env.API_ID || !process.env.API_HASH) {
|
if (!process.env.API_ID || !process.env.API_HASH) {
|
||||||
console.warn('Set API_ID and API_HASH env variables')
|
console.warn('Set API_ID and API_HASH env variables')
|
||||||
|
|
|
@ -38,8 +38,7 @@ import { BinaryWriter } from './utils/binary/binary-writer'
|
||||||
import { encodeUrlSafeBase64, parseUrlSafeBase64 } from './utils/buffer-utils'
|
import { encodeUrlSafeBase64, parseUrlSafeBase64 } from './utils/buffer-utils'
|
||||||
import { BinaryReader } from './utils/binary/binary-reader'
|
import { BinaryReader } from './utils/binary/binary-reader'
|
||||||
import EventEmitter from 'events'
|
import EventEmitter from 'events'
|
||||||
|
import { LogManager } from './utils/logger'
|
||||||
const debug = require('debug')('mtcute:base')
|
|
||||||
|
|
||||||
export namespace BaseTelegramClient {
|
export namespace BaseTelegramClient {
|
||||||
export interface Options {
|
export interface Options {
|
||||||
|
@ -269,6 +268,9 @@ export class BaseTelegramClient extends EventEmitter {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
protected _handleUpdate(update: tl.TypeUpdates): void {}
|
protected _handleUpdate(update: tl.TypeUpdates): void {}
|
||||||
|
|
||||||
|
readonly log = new LogManager()
|
||||||
|
protected readonly _baseLog = this.log.create('base')
|
||||||
|
|
||||||
constructor(opts: BaseTelegramClient.Options) {
|
constructor(opts: BaseTelegramClient.Options) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
|
@ -299,6 +301,8 @@ export class BaseTelegramClient extends EventEmitter {
|
||||||
this._disableUpdates = opts.disableUpdates ?? false
|
this._disableUpdates = opts.disableUpdates ?? false
|
||||||
this._niceStacks = opts.niceStacks ?? true
|
this._niceStacks = opts.niceStacks ?? true
|
||||||
|
|
||||||
|
this.storage.setup?.(this._baseLog)
|
||||||
|
|
||||||
this._layer = opts.overrideLayer ?? tl.CURRENT_LAYER
|
this._layer = opts.overrideLayer ?? tl.CURRENT_LAYER
|
||||||
|
|
||||||
let deviceModel = 'MTCute on '
|
let deviceModel = 'MTCute on '
|
||||||
|
@ -361,7 +365,7 @@ export class BaseTelegramClient extends EventEmitter {
|
||||||
testMode: this._testMode,
|
testMode: this._testMode,
|
||||||
reconnectionStrategy: this._reconnectionStrategy,
|
reconnectionStrategy: this._reconnectionStrategy,
|
||||||
layer: this._layer,
|
layer: this._layer,
|
||||||
})
|
}, this.log.create('connection'))
|
||||||
this.primaryConnection.on('usable', async (isReconnection: boolean) => {
|
this.primaryConnection.on('usable', async (isReconnection: boolean) => {
|
||||||
this._lastUpdateTime = Date.now()
|
this._lastUpdateTime = Date.now()
|
||||||
|
|
||||||
|
@ -595,7 +599,7 @@ export class BaseTelegramClient extends EventEmitter {
|
||||||
lastError = e
|
lastError = e
|
||||||
|
|
||||||
if (e instanceof InternalError) {
|
if (e instanceof InternalError) {
|
||||||
debug('Telegram is having internal issues: %s', e)
|
this._baseLog.warn('Telegram is having internal issues: %s', e)
|
||||||
if (e.message === 'WORKER_BUSY_TOO_LONG_RETRY') {
|
if (e.message === 'WORKER_BUSY_TOO_LONG_RETRY') {
|
||||||
// according to tdlib, "it is dangerous to resend query without timeout, so use 1"
|
// according to tdlib, "it is dangerous to resend query without timeout, so use 1"
|
||||||
await sleep(1000)
|
await sleep(1000)
|
||||||
|
@ -624,7 +628,7 @@ export class BaseTelegramClient extends EventEmitter {
|
||||||
params?.throwFlood !== true &&
|
params?.throwFlood !== true &&
|
||||||
e.seconds <= this._floodSleepThreshold
|
e.seconds <= this._floodSleepThreshold
|
||||||
) {
|
) {
|
||||||
debug('Flood wait for %d seconds', e.seconds)
|
this._baseLog.info('Flood wait for %d seconds', e.seconds)
|
||||||
await sleep(e.seconds * 1000)
|
await sleep(e.seconds * 1000)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -636,14 +640,14 @@ export class BaseTelegramClient extends EventEmitter {
|
||||||
e.constructor === UserMigrateError ||
|
e.constructor === UserMigrateError ||
|
||||||
e.constructor === NetworkMigrateError
|
e.constructor === NetworkMigrateError
|
||||||
) {
|
) {
|
||||||
debug('Migrate error, new dc = %d', e.newDc)
|
this._baseLog.info('Migrate error, new dc = %d', e.newDc)
|
||||||
await this.changeDc(e.newDc)
|
await this.changeDc(e.newDc)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (e.constructor === AuthKeyUnregisteredError) {
|
if (e.constructor === AuthKeyUnregisteredError) {
|
||||||
// we can try re-exporting auth from the primary connection
|
// we can try re-exporting auth from the primary connection
|
||||||
debug('exported auth key error, re-exporting..')
|
this._baseLog.warn('exported auth key error, re-exporting..')
|
||||||
|
|
||||||
const auth = await this.call({
|
const auth = await this.call({
|
||||||
_: 'auth.exportAuthorization',
|
_: 'auth.exportAuthorization',
|
||||||
|
@ -700,14 +704,14 @@ export class BaseTelegramClient extends EventEmitter {
|
||||||
reconnectionStrategy: this._reconnectionStrategy,
|
reconnectionStrategy: this._reconnectionStrategy,
|
||||||
inactivityTimeout,
|
inactivityTimeout,
|
||||||
layer: this._layer,
|
layer: this._layer,
|
||||||
})
|
}, this.log.create('connection'))
|
||||||
|
|
||||||
connection.on('error', (err) => this._emitError(err, connection))
|
connection.on('error', (err) => this._emitError(err, connection))
|
||||||
connection.authKey = await this.storage.getAuthKeyFor(dc.id)
|
connection.authKey = await this.storage.getAuthKeyFor(dc.id)
|
||||||
connection.connect()
|
connection.connect()
|
||||||
|
|
||||||
if (!connection.authKey) {
|
if (!connection.authKey) {
|
||||||
debug('exporting auth to DC %d', dcId)
|
this._baseLog.info('exporting auth to DC %d', dcId)
|
||||||
const auth = await this.call({
|
const auth = await this.call({
|
||||||
_: 'auth.exportAuthorization',
|
_: 'auth.exportAuthorization',
|
||||||
dcId,
|
dcId,
|
||||||
|
@ -807,7 +811,7 @@ export class BaseTelegramClient extends EventEmitter {
|
||||||
const parsedPeers: ITelegramStorage.PeerInfo[] = []
|
const parsedPeers: ITelegramStorage.PeerInfo[] = []
|
||||||
|
|
||||||
let hadMin = false
|
let hadMin = false
|
||||||
|
let count = 0
|
||||||
for (const peer of getAllPeersFrom(obj)) {
|
for (const peer of getAllPeersFrom(obj)) {
|
||||||
if ((peer as any).min) {
|
if ((peer as any).min) {
|
||||||
// absolutely incredible min peer handling, courtesy of levlam.
|
// absolutely incredible min peer handling, courtesy of levlam.
|
||||||
|
@ -816,6 +820,8 @@ export class BaseTelegramClient extends EventEmitter {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
count += 1
|
||||||
|
|
||||||
switch (peer._) {
|
switch (peer._) {
|
||||||
case 'user':
|
case 'user':
|
||||||
parsedPeers.push({
|
parsedPeers.push({
|
||||||
|
@ -854,6 +860,10 @@ export class BaseTelegramClient extends EventEmitter {
|
||||||
|
|
||||||
await this.storage.updatePeers(parsedPeers)
|
await this.storage.updatePeers(parsedPeers)
|
||||||
|
|
||||||
|
if (count > 0) {
|
||||||
|
this._baseLog.debug('cached %d peers, had min: %b', count, hadMin)
|
||||||
|
}
|
||||||
|
|
||||||
return hadMin
|
return hadMin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,6 @@ import {
|
||||||
bufferToBigInt,
|
bufferToBigInt,
|
||||||
} from '../utils/bigint-utils'
|
} from '../utils/bigint-utils'
|
||||||
|
|
||||||
const debug = require('debug')('mtcute:auth')
|
|
||||||
|
|
||||||
// Heavily based on code from https://github.com/LonamiWebs/Telethon/blob/master/telethon/network/authenticator.py
|
// Heavily based on code from https://github.com/LonamiWebs/Telethon/blob/master/telethon/network/authenticator.py
|
||||||
|
|
||||||
const DH_SAFETY_RANGE = bigIntTwo.pow(2048 - 64)
|
const DH_SAFETY_RANGE = bigIntTwo.pow(2048 - 64)
|
||||||
|
@ -45,9 +43,11 @@ export async function doAuthorization(
|
||||||
return connection.send(writer.result())
|
return connection.send(writer.result())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const log = connection.log.create('auth')
|
||||||
|
|
||||||
const nonce = randomBytes(16)
|
const nonce = randomBytes(16)
|
||||||
// Step 1: PQ request
|
// Step 1: PQ request
|
||||||
debug(
|
log.debug(
|
||||||
'%s: starting PQ handshake, nonce = %h',
|
'%s: starting PQ handshake, nonce = %h',
|
||||||
connection.params.dc.ipAddress,
|
connection.params.dc.ipAddress,
|
||||||
nonce
|
nonce
|
||||||
|
@ -61,7 +61,7 @@ export async function doAuthorization(
|
||||||
if (resPq._ !== 'mt_resPQ') throw new Error('Step 1: answer was ' + resPq._)
|
if (resPq._ !== 'mt_resPQ') throw new Error('Step 1: answer was ' + resPq._)
|
||||||
if (!buffersEqual(resPq.nonce, nonce))
|
if (!buffersEqual(resPq.nonce, nonce))
|
||||||
throw new Error('Step 1: invalid nonce from server')
|
throw new Error('Step 1: invalid nonce from server')
|
||||||
debug('%s: received PQ', connection.params.dc.ipAddress)
|
log.debug('%s: received PQ', connection.params.dc.ipAddress)
|
||||||
|
|
||||||
// Step 2: DH exchange
|
// Step 2: DH exchange
|
||||||
const publicKey = findKeyByFingerprints(resPq.serverPublicKeyFingerprints)
|
const publicKey = findKeyByFingerprints(resPq.serverPublicKeyFingerprints)
|
||||||
|
@ -72,14 +72,14 @@ export async function doAuthorization(
|
||||||
.map((i) => i.toString(16))
|
.map((i) => i.toString(16))
|
||||||
.join(', ')
|
.join(', ')
|
||||||
)
|
)
|
||||||
debug(
|
log.debug(
|
||||||
'%s: found server key, fp = %s',
|
'%s: found server key, fp = %s',
|
||||||
connection.params.dc.ipAddress,
|
connection.params.dc.ipAddress,
|
||||||
publicKey.fingerprint
|
publicKey.fingerprint
|
||||||
)
|
)
|
||||||
|
|
||||||
const [p, q] = await crypto.factorizePQ(resPq.pq)
|
const [p, q] = await crypto.factorizePQ(resPq.pq)
|
||||||
debug('%s: factorized PQ', connection.params.dc.ipAddress)
|
log.debug('%s: factorized PQ', connection.params.dc.ipAddress)
|
||||||
|
|
||||||
const newNonce = randomBytes(32)
|
const newNonce = randomBytes(32)
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ export async function doAuthorization(
|
||||||
dc: dcId
|
dc: dcId
|
||||||
} as tl.mtproto.RawP_q_inner_data_dc)
|
} as tl.mtproto.RawP_q_inner_data_dc)
|
||||||
const encryptedData = await crypto.rsaEncrypt(pqInnerData, publicKey)
|
const encryptedData = await crypto.rsaEncrypt(pqInnerData, publicKey)
|
||||||
debug('%s: requesting DH params', connection.params.dc.ipAddress)
|
log.debug('%s: requesting DH params', connection.params.dc.ipAddress)
|
||||||
|
|
||||||
await sendPlainMessage({
|
await sendPlainMessage({
|
||||||
_: 'mt_reqDHParams',
|
_: 'mt_reqDHParams',
|
||||||
|
@ -131,7 +131,7 @@ export async function doAuthorization(
|
||||||
// throw new Error('Step 2: server DH failed')
|
// throw new Error('Step 2: server DH failed')
|
||||||
// }
|
// }
|
||||||
|
|
||||||
debug('%s: server DH ok', connection.params.dc.ipAddress)
|
log.debug('%s: server DH ok', connection.params.dc.ipAddress)
|
||||||
|
|
||||||
if (serverDhParams.encryptedAnswer.length % 16 != 0)
|
if (serverDhParams.encryptedAnswer.length % 16 != 0)
|
||||||
throw new Error('Step 2: AES block size is invalid')
|
throw new Error('Step 2: AES block size is invalid')
|
||||||
|
@ -234,7 +234,7 @@ export async function doAuthorization(
|
||||||
clientDhInnerWriter.pos = 0
|
clientDhInnerWriter.pos = 0
|
||||||
clientDhInnerWriter.raw(clientDhInnerHash)
|
clientDhInnerWriter.raw(clientDhInnerHash)
|
||||||
|
|
||||||
debug(
|
log.debug(
|
||||||
'%s: sending client DH (timeOffset = %d)',
|
'%s: sending client DH (timeOffset = %d)',
|
||||||
connection.params.dc.ipAddress,
|
connection.params.dc.ipAddress,
|
||||||
timeOffset
|
timeOffset
|
||||||
|
@ -260,7 +260,7 @@ export async function doAuthorization(
|
||||||
if (!buffersEqual(dhGen.serverNonce, resPq.serverNonce))
|
if (!buffersEqual(dhGen.serverNonce, resPq.serverNonce))
|
||||||
throw Error('Step 4: invalid server nonce from server')
|
throw Error('Step 4: invalid server nonce from server')
|
||||||
|
|
||||||
debug('%s: DH result: %s', connection.params.dc.ipAddress, dhGen._)
|
log.debug('%s: DH result: %s', connection.params.dc.ipAddress, dhGen._)
|
||||||
|
|
||||||
if (dhGen._ === 'mt_dh_gen_fail') {
|
if (dhGen._ === 'mt_dh_gen_fail') {
|
||||||
// in theory i would be supposed to calculate newNonceHash, but why, we are failing anyway
|
// in theory i would be supposed to calculate newNonceHash, but why, we are failing anyway
|
||||||
|
@ -284,7 +284,7 @@ export async function doAuthorization(
|
||||||
if (!buffersEqual(expectedHash.slice(4, 20), dhGen.newNonceHash1))
|
if (!buffersEqual(expectedHash.slice(4, 20), dhGen.newNonceHash1))
|
||||||
throw Error('Step 4: invalid nonce hash from server')
|
throw Error('Step 4: invalid nonce hash from server')
|
||||||
|
|
||||||
debug('%s: authorization successful', connection.params.dc.ipAddress)
|
log.info('%s: authorization successful', connection.params.dc.ipAddress)
|
||||||
|
|
||||||
return [authKey, serverSalt, timeOffset]
|
return [authKey, serverSalt, timeOffset]
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,7 @@ import {
|
||||||
SerializationCounter,
|
SerializationCounter,
|
||||||
} from '../utils/binary/binary-writer'
|
} from '../utils/binary/binary-writer'
|
||||||
import { BinaryReader } from '../utils/binary/binary-reader'
|
import { BinaryReader } from '../utils/binary/binary-reader'
|
||||||
|
import { Logger } from '../utils/logger'
|
||||||
const debug = require('debug')('mtcute:sess')
|
|
||||||
|
|
||||||
export interface EncryptedMessage {
|
export interface EncryptedMessage {
|
||||||
messageId: BigInteger
|
messageId: BigInteger
|
||||||
|
@ -34,7 +33,7 @@ export class MtprotoSession {
|
||||||
// default salt: [0x00]*8
|
// default salt: [0x00]*8
|
||||||
serverSalt: Buffer = Buffer.alloc(8)
|
serverSalt: Buffer = Buffer.alloc(8)
|
||||||
|
|
||||||
constructor(crypto: ICryptoProvider) {
|
constructor(crypto: ICryptoProvider, readonly log: Logger) {
|
||||||
this._crypto = crypto
|
this._crypto = crypto
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +113,7 @@ export class MtprotoSession {
|
||||||
let encryptedData = reader.raw()
|
let encryptedData = reader.raw()
|
||||||
|
|
||||||
if (!buffersEqual(authKeyId, this._authKeyId!)) {
|
if (!buffersEqual(authKeyId, this._authKeyId!)) {
|
||||||
debug(
|
this.log.warn(
|
||||||
'[%h] warn: received message with unknown authKey = %h (expected %h)',
|
'[%h] warn: received message with unknown authKey = %h (expected %h)',
|
||||||
this._sessionId,
|
this._sessionId,
|
||||||
authKeyId,
|
authKeyId,
|
||||||
|
@ -144,8 +143,8 @@ export class MtprotoSession {
|
||||||
)
|
)
|
||||||
).slice(8, 24)
|
).slice(8, 24)
|
||||||
if (!buffersEqual(messageKey, expectedMessageKey)) {
|
if (!buffersEqual(messageKey, expectedMessageKey)) {
|
||||||
debug(
|
this.log.warn(
|
||||||
'[%h] warn: received message with invalid messageKey = %h (expected %h)',
|
'[%h] received message with invalid messageKey = %h (expected %h)',
|
||||||
this._sessionId,
|
this._sessionId,
|
||||||
messageKey,
|
messageKey,
|
||||||
expectedMessageKey
|
expectedMessageKey
|
||||||
|
@ -159,8 +158,8 @@ export class MtprotoSession {
|
||||||
const messageId = innerReader.long(true)
|
const messageId = innerReader.long(true)
|
||||||
|
|
||||||
if (!buffersEqual(sessionId, this._sessionId)) {
|
if (!buffersEqual(sessionId, this._sessionId)) {
|
||||||
debug(
|
this.log.warn(
|
||||||
'warn: ignoring message with invalid sessionId = %h',
|
'ignoring message with invalid sessionId = %h',
|
||||||
sessionId
|
sessionId
|
||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
|
@ -170,8 +169,8 @@ export class MtprotoSession {
|
||||||
const length = innerReader.uint32()
|
const length = innerReader.uint32()
|
||||||
|
|
||||||
if (length > innerData.length - 32 /* header size */) {
|
if (length > innerData.length - 32 /* header size */) {
|
||||||
debug(
|
this.log.warn(
|
||||||
'warn: ignoring message with invalid length: %d > %d',
|
'ignoring message with invalid length: %d > %d',
|
||||||
length,
|
length,
|
||||||
innerData.length - 32
|
innerData.length - 32
|
||||||
)
|
)
|
||||||
|
@ -179,8 +178,8 @@ export class MtprotoSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (length % 4 !== 0) {
|
if (length % 4 !== 0) {
|
||||||
debug(
|
this.log.warn(
|
||||||
'warn: ignoring message with invalid length: %d is not a multiple of 4',
|
'ignoring message with invalid length: %d is not a multiple of 4',
|
||||||
length
|
length
|
||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
|
@ -191,8 +190,8 @@ export class MtprotoSession {
|
||||||
const paddingSize = innerData.length - innerReader.pos
|
const paddingSize = innerData.length - innerReader.pos
|
||||||
|
|
||||||
if (paddingSize < 12 || paddingSize > 1024) {
|
if (paddingSize < 12 || paddingSize > 1024) {
|
||||||
debug(
|
this.log.warn(
|
||||||
'warn: ignoring message with invalid padding size: %d',
|
'ignoring message with invalid padding size: %d',
|
||||||
paddingSize
|
paddingSize
|
||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -7,8 +7,7 @@ import {
|
||||||
createControllablePromise,
|
createControllablePromise,
|
||||||
} from '../utils/controllable-promise'
|
} from '../utils/controllable-promise'
|
||||||
import { ICryptoProvider } from '../utils/crypto'
|
import { ICryptoProvider } from '../utils/crypto'
|
||||||
|
import { Logger } from '../utils/logger'
|
||||||
const debug = require('debug')('mtcute:conn')
|
|
||||||
|
|
||||||
export interface PersistentConnectionParams {
|
export interface PersistentConnectionParams {
|
||||||
crypto: ICryptoProvider
|
crypto: ICryptoProvider
|
||||||
|
@ -51,7 +50,7 @@ export abstract class PersistentConnection extends EventEmitter {
|
||||||
|
|
||||||
protected abstract onMessage(data: Buffer): void
|
protected abstract onMessage(data: Buffer): void
|
||||||
|
|
||||||
protected constructor(params: PersistentConnectionParams) {
|
protected constructor(params: PersistentConnectionParams, readonly log: Logger) {
|
||||||
super()
|
super()
|
||||||
this.params = params
|
this.params = params
|
||||||
this.changeTransport(params.transportFactory)
|
this.changeTransport(params.transportFactory)
|
||||||
|
@ -63,7 +62,7 @@ export abstract class PersistentConnection extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
this._transport = factory()
|
this._transport = factory()
|
||||||
this._transport.setupCrypto?.(this.params.crypto)
|
this._transport.setup?.(this.params.crypto, this.log)
|
||||||
|
|
||||||
this._transport.on('ready', this.onTransportReady.bind(this))
|
this._transport.on('ready', this.onTransportReady.bind(this))
|
||||||
this._transport.on('message', this.onTransportMessage.bind(this))
|
this._transport.on('message', this.onTransportMessage.bind(this))
|
||||||
|
@ -175,7 +174,7 @@ export abstract class PersistentConnection extends EventEmitter {
|
||||||
if (!this.params.inactivityTimeout) return
|
if (!this.params.inactivityTimeout) return
|
||||||
if (this._inactivityTimeout) clearTimeout(this._inactivityTimeout)
|
if (this._inactivityTimeout) clearTimeout(this._inactivityTimeout)
|
||||||
this._inactivityTimeout = setTimeout(() => {
|
this._inactivityTimeout = setTimeout(() => {
|
||||||
debug(
|
this.log.info(
|
||||||
'disconnected because of inactivity for %d',
|
'disconnected because of inactivity for %d',
|
||||||
this.params.inactivityTimeout
|
this.params.inactivityTimeout
|
||||||
)
|
)
|
||||||
|
|
|
@ -23,6 +23,7 @@ import {
|
||||||
RpcTimeoutError,
|
RpcTimeoutError,
|
||||||
} from '@mtcute/tl/errors'
|
} from '@mtcute/tl/errors'
|
||||||
import { LruStringSet } from '../utils/lru-string-set'
|
import { LruStringSet } from '../utils/lru-string-set'
|
||||||
|
import { Logger } from '../utils/logger'
|
||||||
|
|
||||||
function makeNiceStack(error: RpcError, stack: string, method?: string) {
|
function makeNiceStack(error: RpcError, stack: string, method?: string) {
|
||||||
error.stack = `${error.constructor.name} (${error.code} ${error.text}): ${
|
error.stack = `${error.constructor.name} (${error.code} ${error.text}): ${
|
||||||
|
@ -30,14 +31,6 @@ function makeNiceStack(error: RpcError, stack: string, method?: string) {
|
||||||
}\n at ${method}\n${stack.split('\n').slice(2).join('\n')}`
|
}\n at ${method}\n${stack.split('\n').slice(2).join('\n')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const _debug = require('debug')
|
|
||||||
const debug = _debug('mtcute:conn')
|
|
||||||
|
|
||||||
// hex formatting buffers with %h
|
|
||||||
_debug.formatters.h = (v: Buffer): string => v.toString('hex')
|
|
||||||
// true/false formatting with %b
|
|
||||||
_debug.formatters.b = (v: any): string => !!v + ''
|
|
||||||
|
|
||||||
export interface TelegramConnectionParams extends PersistentConnectionParams {
|
export interface TelegramConnectionParams extends PersistentConnectionParams {
|
||||||
initConnection: tl.RawInitConnectionRequest
|
initConnection: tl.RawInitConnectionRequest
|
||||||
inactivityTimeout?: number
|
inactivityTimeout?: number
|
||||||
|
@ -112,9 +105,9 @@ export class TelegramConnection extends PersistentConnection {
|
||||||
this._pendingAcks = []
|
this._pendingAcks = []
|
||||||
}, 500)
|
}, 500)
|
||||||
|
|
||||||
constructor(params: TelegramConnectionParams) {
|
constructor(params: TelegramConnectionParams, log: Logger) {
|
||||||
super(params)
|
super(params, log)
|
||||||
this._mtproto = new MtprotoSession(this.params.crypto)
|
this._mtproto = new MtprotoSession(this.params.crypto, log.create('session'))
|
||||||
}
|
}
|
||||||
|
|
||||||
onTransportClose(): void {
|
onTransportClose(): void {
|
||||||
|
@ -163,7 +156,7 @@ export class TelegramConnection extends PersistentConnection {
|
||||||
protected onError(error: Error): void {
|
protected onError(error: Error): void {
|
||||||
// https://core.telegram.org/mtproto/mtproto-_transports#_transport-errors
|
// https://core.telegram.org/mtproto/mtproto-_transports#_transport-errors
|
||||||
if (error instanceof TransportError) {
|
if (error instanceof TransportError) {
|
||||||
debug('transport error %d (dc %d)', error.code, this.params.dc.id)
|
this.log.error('transport error %d (dc %d)', error.code, this.params.dc.id)
|
||||||
if (error.code === 404) {
|
if (error.code === 404) {
|
||||||
this._mtproto.reset()
|
this._mtproto.reset()
|
||||||
this.emit('key-change', null)
|
this.emit('key-change', null)
|
||||||
|
@ -221,7 +214,7 @@ export class TelegramConnection extends PersistentConnection {
|
||||||
// if a message is received before authorization,
|
// if a message is received before authorization,
|
||||||
// either the server is misbehaving,
|
// either the server is misbehaving,
|
||||||
// or there was a problem with authorization.
|
// or there was a problem with authorization.
|
||||||
debug('warn: received message before authorization')
|
this.log.warn('warn: received message before authorization')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,7 +234,7 @@ export class TelegramConnection extends PersistentConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _resend(it: PendingMessage, id?: string): void {
|
private _resend(it: PendingMessage, id?: string): void {
|
||||||
debug('resending %s', it.method)
|
this.log.debug('resending %s', it.method)
|
||||||
this._sendBufferForResult(it).catch(it.promise.reject)
|
this._sendBufferForResult(it).catch(it.promise.reject)
|
||||||
if (it.cancel) clearTimeout(it.cancel)
|
if (it.cancel) clearTimeout(it.cancel)
|
||||||
if (id) delete this._pendingRpcCalls[id]
|
if (id) delete this._pendingRpcCalls[id]
|
||||||
|
@ -260,7 +253,7 @@ export class TelegramConnection extends PersistentConnection {
|
||||||
this.onConnectionUsable()
|
this.onConnectionUsable()
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
debug('Authorization error: %s', err.message)
|
this.log.error('Authorization error: %s', err.message)
|
||||||
this.onError(err)
|
this.onError(err)
|
||||||
this.reconnect()
|
this.reconnect()
|
||||||
})
|
})
|
||||||
|
@ -271,7 +264,7 @@ export class TelegramConnection extends PersistentConnection {
|
||||||
messageId: BigInteger
|
messageId: BigInteger
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (messageId.isEven()) {
|
if (messageId.isEven()) {
|
||||||
debug(
|
this.log.warn(
|
||||||
'warn: ignoring message with invalid messageId = %s (is even)',
|
'warn: ignoring message with invalid messageId = %s (is even)',
|
||||||
messageId
|
messageId
|
||||||
)
|
)
|
||||||
|
@ -294,7 +287,7 @@ export class TelegramConnection extends PersistentConnection {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
debug('unknown message received: %o', message)
|
this.log.warn('unknown message received: %o', message)
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleContainer(message: tl.mtproto.RawMsg_container): void {
|
private _handleContainer(message: tl.mtproto.RawMsg_container): void {
|
||||||
|
@ -310,10 +303,10 @@ export class TelegramConnection extends PersistentConnection {
|
||||||
const reqMsgId = message.reqMsgId.toString(16)
|
const reqMsgId = message.reqMsgId.toString(16)
|
||||||
const pending = this._pendingRpcCalls[reqMsgId]
|
const pending = this._pendingRpcCalls[reqMsgId]
|
||||||
if (!pending) {
|
if (!pending) {
|
||||||
debug('received rpc result for unknown message %s', reqMsgId)
|
this.log.warn('received rpc result for unknown message %s', reqMsgId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
debug('handling rpc result for %s (%s)', reqMsgId, pending.method)
|
this.log.request('<<< (%s) %j', pending.method, message.result)
|
||||||
|
|
||||||
if (message.result._ === 'mt_rpc_error') {
|
if (message.result._ === 'mt_rpc_error') {
|
||||||
const error = createRpcErrorFromTl(message.result)
|
const error = createRpcErrorFromTl(message.result)
|
||||||
|
@ -332,7 +325,7 @@ export class TelegramConnection extends PersistentConnection {
|
||||||
private _handlePong(message: tl.mtproto.RawPong): void {
|
private _handlePong(message: tl.mtproto.RawPong): void {
|
||||||
const msgId = message.msgId.toString(16)
|
const msgId = message.msgId.toString(16)
|
||||||
|
|
||||||
debug('handling pong for %s (ping id %s)', msgId, message.pingId)
|
this.log.debug('handling pong for %s (ping id %s)', msgId, message.pingId)
|
||||||
|
|
||||||
if (this._pendingPing && message.pingId.eq(this._pendingPing)) {
|
if (this._pendingPing && message.pingId.eq(this._pendingPing)) {
|
||||||
this._pendingPing = null
|
this._pendingPing = null
|
||||||
|
@ -347,7 +340,7 @@ export class TelegramConnection extends PersistentConnection {
|
||||||
pending.promise.resolve(message)
|
pending.promise.resolve(message)
|
||||||
delete this._pendingRpcCalls[msgId]
|
delete this._pendingRpcCalls[msgId]
|
||||||
} else {
|
} else {
|
||||||
debug('pong to unknown ping %o', message)
|
this.log.warn('pong to unknown ping %o', message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,7 +349,7 @@ export class TelegramConnection extends PersistentConnection {
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const badMsgId = message.badMsgId.toString(16)
|
const badMsgId = message.badMsgId.toString(16)
|
||||||
|
|
||||||
debug(
|
this.log.debug(
|
||||||
'handling bad_server_salt for msg %s, new salt: %h',
|
'handling bad_server_salt for msg %s, new salt: %h',
|
||||||
badMsgId,
|
badMsgId,
|
||||||
message.newServerSalt
|
message.newServerSalt
|
||||||
|
@ -375,7 +368,7 @@ export class TelegramConnection extends PersistentConnection {
|
||||||
this._pendingPing = null
|
this._pendingPing = null
|
||||||
this._pendingPingMsgId = null
|
this._pendingPingMsgId = null
|
||||||
} else {
|
} else {
|
||||||
debug('bad_server_salt to unknown message %o', message)
|
this.log.warn('bad_server_salt to unknown message %o', message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,7 +378,7 @@ export class TelegramConnection extends PersistentConnection {
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const badMsgId = message.badMsgId.toString(16)
|
const badMsgId = message.badMsgId.toString(16)
|
||||||
|
|
||||||
debug('handling bad_msg_notification, code: %d', message.errorCode)
|
this.log.debug('handling bad_msg_notification, code: %d', message.errorCode)
|
||||||
|
|
||||||
if (message.errorCode === 16 || message.errorCode === 17) {
|
if (message.errorCode === 16 || message.errorCode === 17) {
|
||||||
// msg_id is either too high or too low
|
// msg_id is either too high or too low
|
||||||
|
@ -415,7 +408,7 @@ export class TelegramConnection extends PersistentConnection {
|
||||||
if (this._pendingRpcCalls[badMsgId]) {
|
if (this._pendingRpcCalls[badMsgId]) {
|
||||||
this._resend(this._pendingRpcCalls[badMsgId], badMsgId)
|
this._resend(this._pendingRpcCalls[badMsgId], badMsgId)
|
||||||
} else {
|
} else {
|
||||||
debug('bad_msg_notification to unknown message %s', badMsgId)
|
this.log.warn('bad_msg_notification to unknown message %s', badMsgId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -452,7 +445,7 @@ export class TelegramConnection extends PersistentConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug(
|
this.log.debug(
|
||||||
'handling new_session_created (sid = %h), salt: %h',
|
'handling new_session_created (sid = %h), salt: %h',
|
||||||
this._mtproto._sessionId,
|
this._mtproto._sessionId,
|
||||||
message.serverSalt
|
message.serverSalt
|
||||||
|
@ -463,7 +456,7 @@ export class TelegramConnection extends PersistentConnection {
|
||||||
private _handleDetailedInfo(
|
private _handleDetailedInfo(
|
||||||
message: tl.mtproto.RawMsg_detailed_info
|
message: tl.mtproto.RawMsg_detailed_info
|
||||||
): void {
|
): void {
|
||||||
debug(
|
this.log.debug(
|
||||||
'handling msg_detailed_info (sid = %h), msgId = %s',
|
'handling msg_detailed_info (sid = %h), msgId = %s',
|
||||||
this._mtproto._sessionId,
|
this._mtproto._sessionId,
|
||||||
message.answerMsgId
|
message.answerMsgId
|
||||||
|
@ -487,7 +480,7 @@ export class TelegramConnection extends PersistentConnection {
|
||||||
private _handleNewDetailedInfo(
|
private _handleNewDetailedInfo(
|
||||||
message: tl.mtproto.RawMsg_new_detailed_info
|
message: tl.mtproto.RawMsg_new_detailed_info
|
||||||
): void {
|
): void {
|
||||||
debug(
|
this.log.debug(
|
||||||
'handling msg_new_detailed_info (sid = %h), msgId = %s',
|
'handling msg_new_detailed_info (sid = %h), msgId = %s',
|
||||||
this._mtproto._sessionId,
|
this._mtproto._sessionId,
|
||||||
message.answerMsgId
|
message.answerMsgId
|
||||||
|
@ -510,7 +503,7 @@ export class TelegramConnection extends PersistentConnection {
|
||||||
|
|
||||||
private _handleFutureSalts(message: tl.mtproto.RawFuture_salts): void {
|
private _handleFutureSalts(message: tl.mtproto.RawFuture_salts): void {
|
||||||
// TODO actually handle these salts
|
// TODO actually handle these salts
|
||||||
debug(
|
this.log.debug(
|
||||||
'handling future_salts (sid = %h), msgId = %s, %d salts',
|
'handling future_salts (sid = %h), msgId = %s, %d salts',
|
||||||
this._mtproto._sessionId,
|
this._mtproto._sessionId,
|
||||||
message.reqMsgId,
|
message.reqMsgId,
|
||||||
|
@ -663,11 +656,11 @@ export class TelegramConnection extends PersistentConnection {
|
||||||
if (this._usable && this.params.inactivityTimeout)
|
if (this._usable && this.params.inactivityTimeout)
|
||||||
this._rescheduleInactivity()
|
this._rescheduleInactivity()
|
||||||
|
|
||||||
debug('making rpc call %s', message._)
|
this.log.request('>>> %j', message)
|
||||||
|
|
||||||
let obj: tl.TlObject = message
|
let obj: tl.TlObject = message
|
||||||
if (!this._initConnectionCalled) {
|
if (!this._initConnectionCalled) {
|
||||||
debug('wrapping %s with initConnection', message._)
|
this.log.debug('wrapping %s with initConnection', message._)
|
||||||
obj = {
|
obj = {
|
||||||
_: 'invokeWithLayer',
|
_: 'invokeWithLayer',
|
||||||
layer: this.params.layer,
|
layer: this.params.layer,
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { tl } from '@mtcute/tl'
|
||||||
import { MaybeAsync } from '../../types/utils'
|
import { MaybeAsync } from '../../types/utils'
|
||||||
import { ICryptoProvider } from '../../utils/crypto'
|
import { ICryptoProvider } from '../../utils/crypto'
|
||||||
import EventEmitter from 'events'
|
import EventEmitter from 'events'
|
||||||
|
import { Logger } from '../../utils/logger'
|
||||||
|
|
||||||
export enum TransportState {
|
export enum TransportState {
|
||||||
/**
|
/**
|
||||||
|
@ -50,10 +51,12 @@ export interface ITelegramTransport extends EventEmitter {
|
||||||
send(data: Buffer): Promise<void>
|
send(data: Buffer): Promise<void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For transports whose codecs use crypto functions.
|
* Provides crypto and logging for the transport.
|
||||||
|
* Not done in constructor to simplify factory.
|
||||||
|
*
|
||||||
* This method is called before any other.
|
* This method is called before any other.
|
||||||
*/
|
*/
|
||||||
setupCrypto?(crypto: ICryptoProvider): void
|
setup?(crypto: ICryptoProvider, log: Logger): void
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Transport factory function */
|
/** Transport factory function */
|
||||||
|
@ -88,10 +91,10 @@ export interface IPacketCodec {
|
||||||
on(event: 'packet', handler: (packet: Buffer) => void): void
|
on(event: 'packet', handler: (packet: Buffer) => void): void
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For codecs that use crypto functions.
|
* For codecs that use crypto functions and/or logging.
|
||||||
* This method is called before any other.
|
* This method is called before any other.
|
||||||
*/
|
*/
|
||||||
setupCrypto?(crypto: ICryptoProvider): void
|
setup?(crypto: ICryptoProvider, log: Logger): void
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -4,8 +4,7 @@ import { Socket, connect } from 'net'
|
||||||
import EventEmitter from 'events'
|
import EventEmitter from 'events'
|
||||||
import { ICryptoProvider } from '../../utils/crypto'
|
import { ICryptoProvider } from '../../utils/crypto'
|
||||||
import { IntermediatePacketCodec } from './intermediate'
|
import { IntermediatePacketCodec } from './intermediate'
|
||||||
|
import { Logger } from '../../utils/logger'
|
||||||
const debug = require('debug')('mtcute:tcp')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base for TCP transports.
|
* Base for TCP transports.
|
||||||
|
@ -20,11 +19,13 @@ export abstract class BaseTcpTransport
|
||||||
|
|
||||||
abstract _packetCodec: IPacketCodec
|
abstract _packetCodec: IPacketCodec
|
||||||
protected _crypto!: ICryptoProvider
|
protected _crypto!: ICryptoProvider
|
||||||
|
protected log!: Logger
|
||||||
|
|
||||||
packetCodecInitialized = false
|
packetCodecInitialized = false
|
||||||
|
|
||||||
setupCrypto(crypto: ICryptoProvider): void {
|
setup(crypto: ICryptoProvider, log: Logger): void {
|
||||||
this._crypto = crypto
|
this._crypto = crypto
|
||||||
|
this.log = log.create('tcp')
|
||||||
}
|
}
|
||||||
|
|
||||||
state(): TransportState {
|
state(): TransportState {
|
||||||
|
@ -35,12 +36,13 @@ export abstract class BaseTcpTransport
|
||||||
return this._currentDc
|
return this._currentDc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
connect(dc: tl.RawDcOption, testMode: boolean): void {
|
connect(dc: tl.RawDcOption, testMode: boolean): void {
|
||||||
if (this._state !== TransportState.Idle)
|
if (this._state !== TransportState.Idle)
|
||||||
throw new Error('Transport is not IDLE')
|
throw new Error('Transport is not IDLE')
|
||||||
|
|
||||||
if (!this.packetCodecInitialized) {
|
if (!this.packetCodecInitialized) {
|
||||||
this._packetCodec.setupCrypto?.(this._crypto)
|
this._packetCodec.setup?.(this._crypto, this.log)
|
||||||
this._packetCodec.on('error', (err) => this.emit('error', err))
|
this._packetCodec.on('error', (err) => this.emit('error', err))
|
||||||
this._packetCodec.on('packet', (buf) => this.emit('message', buf))
|
this._packetCodec.on('packet', (buf) => this.emit('message', buf))
|
||||||
this.packetCodecInitialized = true
|
this.packetCodecInitialized = true
|
||||||
|
@ -61,7 +63,7 @@ export abstract class BaseTcpTransport
|
||||||
|
|
||||||
close(): void {
|
close(): void {
|
||||||
if (this._state === TransportState.Idle) return
|
if (this._state === TransportState.Idle) return
|
||||||
debug('%s: close', this._currentDc!.ipAddress)
|
this.log.debug('%s: close', this._currentDc!.ipAddress)
|
||||||
|
|
||||||
this.emit('close')
|
this.emit('close')
|
||||||
this._state = TransportState.Idle
|
this._state = TransportState.Idle
|
||||||
|
@ -73,12 +75,12 @@ export abstract class BaseTcpTransport
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleError(error: Error): Promise<void> {
|
async handleError(error: Error): Promise<void> {
|
||||||
debug('%s: error: %s', this._currentDc!.ipAddress, error.stack)
|
this.log.error('%s: error: %s', this._currentDc!.ipAddress, error.stack)
|
||||||
this.emit('error', error)
|
this.emit('error', error)
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleConnect(): Promise<void> {
|
async handleConnect(): Promise<void> {
|
||||||
debug('%s: connected', this._currentDc!.ipAddress)
|
this.log.debug('%s: connected', this._currentDc!.ipAddress)
|
||||||
const initialMessage = await this._packetCodec.tag()
|
const initialMessage = await this._packetCodec.tag()
|
||||||
|
|
||||||
if (initialMessage.length) {
|
if (initialMessage.length) {
|
||||||
|
|
|
@ -6,8 +6,7 @@ import { ICryptoProvider } from '../../utils/crypto'
|
||||||
import type WebSocket from 'ws'
|
import type WebSocket from 'ws'
|
||||||
import { IntermediatePacketCodec } from './intermediate'
|
import { IntermediatePacketCodec } from './intermediate'
|
||||||
import { ObfuscatedPacketCodec } from './obfuscated'
|
import { ObfuscatedPacketCodec } from './obfuscated'
|
||||||
|
import { Logger } from '../../utils/logger'
|
||||||
const debug = require('debug')('mtcute:ws')
|
|
||||||
|
|
||||||
let ws: {
|
let ws: {
|
||||||
new (address: string, options?: string): WebSocket
|
new (address: string, options?: string): WebSocket
|
||||||
|
@ -41,6 +40,7 @@ export abstract class BaseWebSocketTransport
|
||||||
private _state: TransportState = TransportState.Idle
|
private _state: TransportState = TransportState.Idle
|
||||||
private _socket: WebSocket | null = null
|
private _socket: WebSocket | null = null
|
||||||
private _crypto!: ICryptoProvider
|
private _crypto!: ICryptoProvider
|
||||||
|
protected log!: Logger
|
||||||
|
|
||||||
abstract _packetCodec: IPacketCodec
|
abstract _packetCodec: IPacketCodec
|
||||||
packetCodecInitialized = false
|
packetCodecInitialized = false
|
||||||
|
@ -69,8 +69,9 @@ export abstract class BaseWebSocketTransport
|
||||||
this.close = this.close.bind(this)
|
this.close = this.close.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
setupCrypto(crypto: ICryptoProvider): void {
|
setup(crypto: ICryptoProvider, log: Logger): void {
|
||||||
this._crypto = crypto
|
this._crypto = crypto
|
||||||
|
this.log = log.create('tcp')
|
||||||
}
|
}
|
||||||
|
|
||||||
state(): TransportState {
|
state(): TransportState {
|
||||||
|
@ -86,7 +87,7 @@ export abstract class BaseWebSocketTransport
|
||||||
throw new Error('Transport is not IDLE')
|
throw new Error('Transport is not IDLE')
|
||||||
|
|
||||||
if (!this.packetCodecInitialized) {
|
if (!this.packetCodecInitialized) {
|
||||||
this._packetCodec.setupCrypto?.(this._crypto)
|
this._packetCodec.setup?.(this._crypto, this.log)
|
||||||
this._packetCodec.on('error', (err) => this.emit('error', err))
|
this._packetCodec.on('error', (err) => this.emit('error', err))
|
||||||
this._packetCodec.on('packet', (buf) => this.emit('message', buf))
|
this._packetCodec.on('packet', (buf) => this.emit('message', buf))
|
||||||
this.packetCodecInitialized = true
|
this.packetCodecInitialized = true
|
||||||
|
@ -113,7 +114,7 @@ export abstract class BaseWebSocketTransport
|
||||||
|
|
||||||
close(): void {
|
close(): void {
|
||||||
if (this._state === TransportState.Idle) return
|
if (this._state === TransportState.Idle) return
|
||||||
debug('%s: close', this._currentDc!.ipAddress)
|
this.log.debug('%s: close', this._currentDc!.ipAddress)
|
||||||
|
|
||||||
this.emit('close')
|
this.emit('close')
|
||||||
this._state = TransportState.Idle
|
this._state = TransportState.Idle
|
||||||
|
@ -125,12 +126,12 @@ export abstract class BaseWebSocketTransport
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleError({ error }: { error: Error }): Promise<void> {
|
async handleError({ error }: { error: Error }): Promise<void> {
|
||||||
debug('%s: error: %s', this._currentDc!.ipAddress, error.stack)
|
this.log.error('%s: error: %s', this._currentDc!.ipAddress, error.stack)
|
||||||
this.emit('error', error)
|
this.emit('error', error)
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleConnect(): Promise<void> {
|
async handleConnect(): Promise<void> {
|
||||||
debug('%s: connected', this._currentDc!.ipAddress)
|
this.log.debug('%s: connected', this._currentDc!.ipAddress)
|
||||||
const initialMessage = await this._packetCodec.tag()
|
const initialMessage = await this._packetCodec.tag()
|
||||||
|
|
||||||
this._socket!.send(initialMessage)
|
this._socket!.send(initialMessage)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import EventEmitter from 'events'
|
import EventEmitter from 'events'
|
||||||
import { IPacketCodec } from './abstract'
|
import { IPacketCodec } from './abstract'
|
||||||
import { ICryptoProvider } from '../../utils/crypto'
|
import { ICryptoProvider } from '../../utils/crypto'
|
||||||
|
import { Logger } from '../../utils/logger'
|
||||||
|
|
||||||
export abstract class WrappedCodec extends EventEmitter {
|
export abstract class WrappedCodec extends EventEmitter {
|
||||||
protected _crypto!: ICryptoProvider
|
protected _crypto!: ICryptoProvider
|
||||||
|
@ -20,8 +21,8 @@ export abstract class WrappedCodec extends EventEmitter {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
setupCrypto(crypto: ICryptoProvider): void {
|
setup(crypto: ICryptoProvider, log: Logger): void {
|
||||||
this._crypto = crypto
|
this._crypto = crypto
|
||||||
this._inner.setupCrypto?.(crypto)
|
this._inner.setup?.(crypto, log)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { BasicPeerType, MaybeAsync } from '../types'
|
import { BasicPeerType, MaybeAsync } from '../types'
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
|
import { Logger } from '../utils/logger'
|
||||||
|
|
||||||
export namespace ITelegramStorage {
|
export namespace ITelegramStorage {
|
||||||
export interface PeerInfo {
|
export interface PeerInfo {
|
||||||
|
@ -31,6 +32,12 @@ export namespace ITelegramStorage {
|
||||||
* write updates to the disk when `save()` is called.
|
* write updates to the disk when `save()` is called.
|
||||||
*/
|
*/
|
||||||
export interface ITelegramStorage {
|
export interface ITelegramStorage {
|
||||||
|
/**
|
||||||
|
* This method is called before any other.
|
||||||
|
* For storages that use logging, logger instance.
|
||||||
|
*/
|
||||||
|
setup?(log: Logger): void
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load session from some external storage.
|
* Load session from some external storage.
|
||||||
* Should be used either to load session content from file/network/etc
|
* Should be used either to load session content from file/network/etc
|
||||||
|
|
115
packages/core/src/utils/logger.ts
Normal file
115
packages/core/src/utils/logger.ts
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
import { _defaultLoggingHandler } from './platform/logging'
|
||||||
|
|
||||||
|
let defaultLogLevel = 2
|
||||||
|
if (typeof process !== 'undefined') {
|
||||||
|
const envLogLevel = parseInt(process.env.MTCUTE_LOG_LEVEL!)
|
||||||
|
if (!isNaN(envLogLevel)) {
|
||||||
|
defaultLogLevel = envLogLevel
|
||||||
|
}
|
||||||
|
} else if (typeof localStorage !== 'undefined') {
|
||||||
|
const localLogLevel = parseInt(localStorage.MTCUTE_LOG_LEVEL)
|
||||||
|
if (!isNaN(localLogLevel)) {
|
||||||
|
defaultLogLevel = localLogLevel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const FORMATTER_RE = /%[a-zA-Z]/g
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger created by {@link LogManager}
|
||||||
|
*/
|
||||||
|
export class Logger {
|
||||||
|
private color: number
|
||||||
|
|
||||||
|
constructor(readonly mgr: LogManager, readonly tag: string) {
|
||||||
|
let hash = 0
|
||||||
|
|
||||||
|
for (let i = 0; i < tag.length; i++) {
|
||||||
|
hash = (hash << 5) - hash + tag.charCodeAt(i)
|
||||||
|
hash |= 0 // convert to 32bit int
|
||||||
|
}
|
||||||
|
|
||||||
|
this.color = Math.abs(hash) % 6
|
||||||
|
}
|
||||||
|
|
||||||
|
log(level: number, fmt: string, ...args: any[]): void {
|
||||||
|
if (level > this.mgr.level) return
|
||||||
|
|
||||||
|
// custom formatters
|
||||||
|
if (
|
||||||
|
fmt.indexOf('%h') > -1 ||
|
||||||
|
fmt.indexOf('%b') > -1 ||
|
||||||
|
fmt.indexOf('%j') > -1
|
||||||
|
) {
|
||||||
|
let idx = 0
|
||||||
|
fmt = fmt.replace(FORMATTER_RE, (m) => {
|
||||||
|
if (m === '%h' || m === '%b' || m === '%j') {
|
||||||
|
const val = args[idx]
|
||||||
|
|
||||||
|
args.splice(idx, 1)
|
||||||
|
if (m === '%h') return val.toString('hex')
|
||||||
|
if (m === '%b') return !!val + ''
|
||||||
|
if (m === '%j') return JSON.stringify(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
idx++
|
||||||
|
|
||||||
|
return m
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mgr.handler(this.color, level, this.tag, fmt, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly error = this.log.bind(this, LogManager.ERROR)
|
||||||
|
readonly warn = this.log.bind(this, LogManager.WARN)
|
||||||
|
readonly info = this.log.bind(this, LogManager.INFO)
|
||||||
|
readonly debug = this.log.bind(this, LogManager.DEBUG)
|
||||||
|
readonly request = this.log.bind(this, LogManager.REQUEST)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link Logger} with the given tag
|
||||||
|
* from the same {@link LogManager} as the current
|
||||||
|
* Logger.
|
||||||
|
*
|
||||||
|
* @param tag Logger tag
|
||||||
|
*/
|
||||||
|
create(tag: string): Logger {
|
||||||
|
return this.mgr.create(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log manager.
|
||||||
|
*
|
||||||
|
* Does nothing by itself, but allows managing instance logs
|
||||||
|
*/
|
||||||
|
export class LogManager {
|
||||||
|
static OFF = 0
|
||||||
|
static ERROR = 1
|
||||||
|
static WARN = 2
|
||||||
|
static INFO = 3
|
||||||
|
static DEBUG = 4
|
||||||
|
static REQUEST = 5
|
||||||
|
|
||||||
|
private _cache: Record<string, Logger> = {}
|
||||||
|
|
||||||
|
level = defaultLogLevel
|
||||||
|
handler = _defaultLoggingHandler
|
||||||
|
|
||||||
|
disable(): void {
|
||||||
|
this.level = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link Logger} with the given tag
|
||||||
|
*
|
||||||
|
* @param tag Logger tag
|
||||||
|
*/
|
||||||
|
create(tag: string): Logger {
|
||||||
|
if (!(tag in this._cache)) {
|
||||||
|
this._cache[tag] = new Logger(this, tag)
|
||||||
|
}
|
||||||
|
return this._cache[tag]
|
||||||
|
}
|
||||||
|
}
|
57
packages/core/src/utils/platform/logging.ts
Normal file
57
packages/core/src/utils/platform/logging.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import { isatty } from 'tty'
|
||||||
|
|
||||||
|
const isTty = isatty(process.stdout.fd)
|
||||||
|
|
||||||
|
const BASE_FORMAT = isTty ? '[%s] [%s] %s%s\x1b[0m - ' : '[%s] [%s] %s - '
|
||||||
|
const LEVEL_NAMES = isTty
|
||||||
|
? [
|
||||||
|
'', // OFF
|
||||||
|
'\x1b[31mERR\x1b[0m',
|
||||||
|
'\x1b[33mWRN\x1b[0m',
|
||||||
|
'\x1b[34mINF\x1b[0m',
|
||||||
|
'\x1b[36mDBG\x1b[0m',
|
||||||
|
'\x1b[35mREQ\x1b[0m',
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
'', // OFF
|
||||||
|
'ERR',
|
||||||
|
'WRN',
|
||||||
|
'INF',
|
||||||
|
'DBG',
|
||||||
|
'REQ',
|
||||||
|
]
|
||||||
|
const TAG_COLORS = [6, 2, 3, 4, 5, 1].map((i) => `\x1b[3${i};1m`)
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export const _defaultLoggingHandler = isTty
|
||||||
|
? (
|
||||||
|
color: number,
|
||||||
|
level: number,
|
||||||
|
tag: string,
|
||||||
|
fmt: string,
|
||||||
|
args: any[]
|
||||||
|
): void => {
|
||||||
|
console.log(
|
||||||
|
BASE_FORMAT + fmt,
|
||||||
|
new Date().toISOString(),
|
||||||
|
LEVEL_NAMES[level],
|
||||||
|
TAG_COLORS[color],
|
||||||
|
tag,
|
||||||
|
...args
|
||||||
|
)
|
||||||
|
}
|
||||||
|
: (
|
||||||
|
color: number,
|
||||||
|
level: number,
|
||||||
|
tag: string,
|
||||||
|
fmt: string,
|
||||||
|
args: any[]
|
||||||
|
): void => {
|
||||||
|
console.log(
|
||||||
|
BASE_FORMAT + fmt,
|
||||||
|
new Date().toISOString(),
|
||||||
|
LEVEL_NAMES[level],
|
||||||
|
tag,
|
||||||
|
...args
|
||||||
|
)
|
||||||
|
}
|
46
packages/core/src/utils/platform/logging.web.ts
Normal file
46
packages/core/src/utils/platform/logging.web.ts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
const BASE_FORMAT = '[%s] [%с%s%с] %c%s%c - '
|
||||||
|
const LEVEL_NAMES = [
|
||||||
|
'', // OFF
|
||||||
|
'ERR',
|
||||||
|
'WRN',
|
||||||
|
'INF',
|
||||||
|
'DBG',
|
||||||
|
'REQ'
|
||||||
|
]
|
||||||
|
const COLORS = [
|
||||||
|
'', // OFF
|
||||||
|
'#ff0000',
|
||||||
|
'#ffff00',
|
||||||
|
'#0000ff',
|
||||||
|
'#00ffff',
|
||||||
|
'#ff00ff',
|
||||||
|
]
|
||||||
|
const TAG_COLORS = [
|
||||||
|
'#44ffff',
|
||||||
|
'#44ff44',
|
||||||
|
'#ffff44',
|
||||||
|
'#4444ff',
|
||||||
|
'#ff44ff',
|
||||||
|
'#ff4444',
|
||||||
|
]
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export const _defaultLoggingHandler = (
|
||||||
|
color: number,
|
||||||
|
level: number,
|
||||||
|
tag: string,
|
||||||
|
fmt: string,
|
||||||
|
args: any[]
|
||||||
|
): void => {
|
||||||
|
console.log(
|
||||||
|
BASE_FORMAT + fmt,
|
||||||
|
new Date().toISOString(),
|
||||||
|
COLORS[level],
|
||||||
|
LEVEL_NAMES[level],
|
||||||
|
'',
|
||||||
|
TAG_COLORS[color],
|
||||||
|
tag,
|
||||||
|
'',
|
||||||
|
...args
|
||||||
|
)
|
||||||
|
}
|
|
@ -15,7 +15,6 @@
|
||||||
"@mtcute/tl": "~1.131.0",
|
"@mtcute/tl": "~1.131.0",
|
||||||
"@mtcute/core": "^1.0.0",
|
"@mtcute/core": "^1.0.0",
|
||||||
"@mtcute/client": "^1.0.0",
|
"@mtcute/client": "^1.0.0",
|
||||||
"events": "^3.2.0",
|
"events": "^3.2.0"
|
||||||
"debug": "^4.3.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,6 @@ import {
|
||||||
import { connect as connectTcp } from 'net'
|
import { connect as connectTcp } from 'net'
|
||||||
import { connect as connectTls, SecureContextOptions } from 'tls'
|
import { connect as connectTls, SecureContextOptions } from 'tls'
|
||||||
|
|
||||||
const debug = require('debug')('mtcute:http-proxy')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An error has occurred while connecting to an HTTP(s) proxy
|
* An error has occurred while connecting to an HTTP(s) proxy
|
||||||
*/
|
*/
|
||||||
|
@ -105,7 +103,7 @@ export abstract class BaseHttpProxyTcpTransport extends BaseTcpTransport {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onProxyConnected() {
|
private _onProxyConnected() {
|
||||||
debug(
|
this.log.debug(
|
||||||
'[%s:%d] connected to proxy, sending CONNECT',
|
'[%s:%d] connected to proxy, sending CONNECT',
|
||||||
this._proxy.host,
|
this._proxy.host,
|
||||||
this._proxy.port
|
this._proxy.port
|
||||||
|
@ -135,7 +133,7 @@ export abstract class BaseHttpProxyTcpTransport extends BaseTcpTransport {
|
||||||
|
|
||||||
this._socket!.write(packet)
|
this._socket!.write(packet)
|
||||||
this._socket!.once('data', (msg) => {
|
this._socket!.once('data', (msg) => {
|
||||||
debug(
|
this.log.debug(
|
||||||
'[%s:%d] CONNECT resulted in: %s',
|
'[%s:%d] CONNECT resulted in: %s',
|
||||||
this._proxy.host,
|
this._proxy.host,
|
||||||
this._proxy.port,
|
this._proxy.port,
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
"build": "tsc"
|
"build": "tsc"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mtcute/core": "^1.0.0",
|
"@mtcute/core": "^1.0.0"
|
||||||
"debug": "^4.3.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,6 @@ import { connect } from 'net'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { normalize } from 'ip6'
|
import { normalize } from 'ip6'
|
||||||
|
|
||||||
const debug = require('debug')('mtcute:socks-proxy')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An error has occurred while connecting to an SOCKS proxy
|
* An error has occurred while connecting to an SOCKS proxy
|
||||||
*/
|
*/
|
||||||
|
@ -234,7 +232,7 @@ export abstract class BaseSocksTcpTransport extends BaseTcpTransport {
|
||||||
}
|
}
|
||||||
const code = msg[1]
|
const code = msg[1]
|
||||||
|
|
||||||
debug(
|
this.log.debug(
|
||||||
'[%s:%d] CONNECT returned code %d',
|
'[%s:%d] CONNECT returned code %d',
|
||||||
this._proxy.host,
|
this._proxy.host,
|
||||||
this._proxy.port,
|
this._proxy.port,
|
||||||
|
@ -259,7 +257,7 @@ export abstract class BaseSocksTcpTransport extends BaseTcpTransport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug(
|
this.log.debug(
|
||||||
'[%s:%d] connected to proxy, sending CONNECT',
|
'[%s:%d] connected to proxy, sending CONNECT',
|
||||||
this._proxy.host,
|
this._proxy.host,
|
||||||
this._proxy.port
|
this._proxy.port
|
||||||
|
@ -280,7 +278,7 @@ export abstract class BaseSocksTcpTransport extends BaseTcpTransport {
|
||||||
let state: 'greeting' | 'auth' | 'connect' = 'greeting'
|
let state: 'greeting' | 'auth' | 'connect' = 'greeting'
|
||||||
|
|
||||||
const sendConnect = () => {
|
const sendConnect = () => {
|
||||||
debug(
|
this.log.debug(
|
||||||
'[%s:%d] sending CONNECT',
|
'[%s:%d] sending CONNECT',
|
||||||
this._proxy.host,
|
this._proxy.host,
|
||||||
this._proxy.port
|
this._proxy.port
|
||||||
|
@ -317,7 +315,7 @@ export abstract class BaseSocksTcpTransport extends BaseTcpTransport {
|
||||||
|
|
||||||
const chosen = msg[1]
|
const chosen = msg[1]
|
||||||
|
|
||||||
debug(
|
this.log.debug(
|
||||||
'[%s:%d] GREETING returned auth method %d',
|
'[%s:%d] GREETING returned auth method %d',
|
||||||
this._proxy.host,
|
this._proxy.host,
|
||||||
this._proxy.port,
|
this._proxy.port,
|
||||||
|
@ -386,7 +384,7 @@ export abstract class BaseSocksTcpTransport extends BaseTcpTransport {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
debug(
|
this.log.debug(
|
||||||
'[%s:%d] AUTH returned code %d',
|
'[%s:%d] AUTH returned code %d',
|
||||||
this._proxy.host,
|
this._proxy.host,
|
||||||
this._proxy.port,
|
this._proxy.port,
|
||||||
|
@ -421,7 +419,7 @@ export abstract class BaseSocksTcpTransport extends BaseTcpTransport {
|
||||||
|
|
||||||
const code = msg[1]
|
const code = msg[1]
|
||||||
|
|
||||||
debug(
|
this.log.debug(
|
||||||
'[%s:%d] CONNECT returned code %d',
|
'[%s:%d] CONNECT returned code %d',
|
||||||
this._proxy.host,
|
this._proxy.host,
|
||||||
this._proxy.port,
|
this._proxy.port,
|
||||||
|
@ -451,7 +449,7 @@ export abstract class BaseSocksTcpTransport extends BaseTcpTransport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug(
|
this.log.debug(
|
||||||
'[%s:%d] connected to proxy, sending GREETING',
|
'[%s:%d] connected to proxy, sending GREETING',
|
||||||
this._proxy.host,
|
this._proxy.host,
|
||||||
this._proxy.port
|
this._proxy.port
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mtcute/core": "^1.0.0",
|
"@mtcute/core": "^1.0.0",
|
||||||
"ip6": "^0.2.6",
|
"ip6": "^0.2.6"
|
||||||
"debug": "^4.3.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,7 @@ import { tl } from '@mtcute/tl'
|
||||||
import sqlite3 from 'better-sqlite3'
|
import sqlite3 from 'better-sqlite3'
|
||||||
import bigInt from 'big-integer'
|
import bigInt from 'big-integer'
|
||||||
import { throttle } from '@mtcute/core'
|
import { throttle } from '@mtcute/core'
|
||||||
|
import { Logger } from '@mtcute/core/src/utils/logger'
|
||||||
const debug = require('debug')('mtcute:sqlite')
|
|
||||||
|
|
||||||
function serializeAccessHash(hash: tl.Long): Buffer {
|
function serializeAccessHash(hash: tl.Long): Buffer {
|
||||||
const arr = hash.toArray(256)
|
const arr = hash.toArray(256)
|
||||||
|
@ -181,6 +180,8 @@ export class SqliteStorage implements ITelegramStorage /*, IStateStorage */ {
|
||||||
private _vacuumTimeout?: NodeJS.Timeout
|
private _vacuumTimeout?: NodeJS.Timeout
|
||||||
private _vacuumInterval: number
|
private _vacuumInterval: number
|
||||||
|
|
||||||
|
private log!: Logger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param filename Database file name, or `:memory:` for in-memory DB
|
* @param filename Database file name, or `:memory:` for in-memory DB
|
||||||
* @param params
|
* @param params
|
||||||
|
@ -302,6 +303,10 @@ export class SqliteStorage implements ITelegramStorage /*, IStateStorage */ {
|
||||||
// todo: add support for workers (idk if really needed, but still)
|
// todo: add support for workers (idk if really needed, but still)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setup(log: Logger): void {
|
||||||
|
this.log = log.create('sqlite')
|
||||||
|
}
|
||||||
|
|
||||||
private _readFullPeer(data: Buffer): tl.TypeUser | tl.TypeChat | null {
|
private _readFullPeer(data: Buffer): tl.TypeUser | tl.TypeChat | null {
|
||||||
// reuse reader because why not
|
// reuse reader because why not
|
||||||
this._reader.pos = 0
|
this._reader.pos = 0
|
||||||
|
@ -368,14 +373,14 @@ export class SqliteStorage implements ITelegramStorage /*, IStateStorage */ {
|
||||||
// tables already exist, check version
|
// tables already exist, check version
|
||||||
this._initializeStatements()
|
this._initializeStatements()
|
||||||
const version = this._getFromKv('ver')
|
const version = this._getFromKv('ver')
|
||||||
debug('current db version = %d', version)
|
this.log.debug('current db version = %d', version)
|
||||||
if (version < CURRENT_VERSION) {
|
if (version < CURRENT_VERSION) {
|
||||||
this._upgradeDatabase(version)
|
this._upgradeDatabase(version)
|
||||||
this._setToKv('ver', CURRENT_VERSION, true)
|
this._setToKv('ver', CURRENT_VERSION, true)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// create tables
|
// create tables
|
||||||
debug('creating tables')
|
this.log.debug('creating tables')
|
||||||
this._db.exec(SCHEMA)
|
this._db.exec(SCHEMA)
|
||||||
this._initializeStatements()
|
this._initializeStatements()
|
||||||
this._setToKv('ver', CURRENT_VERSION, true)
|
this._setToKv('ver', CURRENT_VERSION, true)
|
||||||
|
@ -389,7 +394,7 @@ export class SqliteStorage implements ITelegramStorage /*, IStateStorage */ {
|
||||||
|
|
||||||
load(): void {
|
load(): void {
|
||||||
this._db = sqlite3(this._filename, {
|
this._db = sqlite3(this._filename, {
|
||||||
verbose: debug.enabled ? debug : null,
|
verbose: this.log.mgr.level === 4 ? this.log.debug : undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
this._initialize()
|
this._initialize()
|
||||||
|
|
|
@ -15,8 +15,7 @@
|
||||||
"@mtcute/core": "^1.0.0",
|
"@mtcute/core": "^1.0.0",
|
||||||
"@mtcute/tl": "~1.131.0",
|
"@mtcute/tl": "~1.131.0",
|
||||||
"better-sqlite3": "^7.4.0",
|
"better-sqlite3": "^7.4.0",
|
||||||
"big-integer": "1.6.48",
|
"big-integer": "1.6.48"
|
||||||
"debug": "^4.3.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/sqlite3": "^3.1.7",
|
"@types/sqlite3": "^3.1.7",
|
||||||
|
|
Loading…
Reference in a new issue