refactor: extracted dc config management to separate class
This commit is contained in:
parent
6a2c5d90b7
commit
4848c4e62d
2 changed files with 117 additions and 80 deletions
|
@ -35,6 +35,8 @@ import {
|
||||||
import { addPublicKey } from './utils/crypto/keys'
|
import { addPublicKey } from './utils/crypto/keys'
|
||||||
import { readStringSession, writeStringSession } from './utils/string-session'
|
import { readStringSession, writeStringSession } from './utils/string-session'
|
||||||
|
|
||||||
|
import { ConfigManager } from './network/config-manager'
|
||||||
|
|
||||||
export interface BaseTelegramClientOptions {
|
export interface BaseTelegramClientOptions {
|
||||||
/**
|
/**
|
||||||
* API ID from my.telegram.org
|
* API ID from my.telegram.org
|
||||||
|
@ -247,8 +249,9 @@ export class BaseTelegramClient extends EventEmitter {
|
||||||
protected _lastUpdateTime = 0
|
protected _lastUpdateTime = 0
|
||||||
private _floodWaitedRequests: Record<string, number> = {}
|
private _floodWaitedRequests: Record<string, number> = {}
|
||||||
|
|
||||||
protected _config?: tl.RawConfig
|
protected _config = new ConfigManager(() =>
|
||||||
protected _cdnConfig?: tl.RawCdnConfig
|
this.call({ _: 'help.getConfig' })
|
||||||
|
)
|
||||||
|
|
||||||
private _additionalConnections: SessionConnection[] = []
|
private _additionalConnections: SessionConnection[] = []
|
||||||
|
|
||||||
|
@ -499,6 +502,8 @@ export class BaseTelegramClient extends EventEmitter {
|
||||||
async close(): Promise<void> {
|
async close(): Promise<void> {
|
||||||
await this._onClose()
|
await this._onClose()
|
||||||
|
|
||||||
|
this._config.destroy()
|
||||||
|
|
||||||
this._cleanupPrimaryConnection(true)
|
this._cleanupPrimaryConnection(true)
|
||||||
// close additional connections
|
// close additional connections
|
||||||
this._additionalConnections.forEach((conn) => conn.destroy())
|
this._additionalConnections.forEach((conn) => conn.destroy())
|
||||||
|
@ -507,82 +512,6 @@ export class BaseTelegramClient extends EventEmitter {
|
||||||
await this.storage.destroy?.()
|
await this.storage.destroy?.()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility function to find the DC by its ID.
|
|
||||||
*
|
|
||||||
* @param id Datacenter ID
|
|
||||||
* @param preferMedia Whether to prefer media-only DCs
|
|
||||||
* @param cdn Whether the needed DC is a CDN DC
|
|
||||||
*/
|
|
||||||
async getDcById(
|
|
||||||
id: number,
|
|
||||||
preferMedia = false,
|
|
||||||
cdn = false,
|
|
||||||
): Promise<tl.RawDcOption> {
|
|
||||||
if (!this._config) {
|
|
||||||
this._config = await this.call({ _: 'help.getConfig' })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cdn && !this._cdnConfig) {
|
|
||||||
this._cdnConfig = await this.call({ _: 'help.getCdnConfig' })
|
|
||||||
|
|
||||||
for (const key of this._cdnConfig.publicKeys) {
|
|
||||||
await addPublicKey(this._crypto, key.publicKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._useIpv6) {
|
|
||||||
// first try to find ipv6 dc
|
|
||||||
|
|
||||||
let found
|
|
||||||
|
|
||||||
if (preferMedia) {
|
|
||||||
found = this._config.dcOptions.find(
|
|
||||||
(it) =>
|
|
||||||
it.id === id &&
|
|
||||||
it.mediaOnly &&
|
|
||||||
it.cdn === cdn &&
|
|
||||||
it.ipv6 &&
|
|
||||||
!it.tcpoOnly,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
found = this._config.dcOptions.find(
|
|
||||||
(it) =>
|
|
||||||
it.id === id &&
|
|
||||||
it.cdn === cdn &&
|
|
||||||
it.ipv6 &&
|
|
||||||
!it.tcpoOnly,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (found) return found
|
|
||||||
}
|
|
||||||
|
|
||||||
let found
|
|
||||||
|
|
||||||
if (preferMedia) {
|
|
||||||
found = this._config.dcOptions.find(
|
|
||||||
(it) =>
|
|
||||||
it.id === id &&
|
|
||||||
it.mediaOnly &&
|
|
||||||
it.cdn === cdn &&
|
|
||||||
!it.tcpoOnly &&
|
|
||||||
!it.ipv6,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (!found) {
|
|
||||||
found = this._config.dcOptions.find(
|
|
||||||
(it) =>
|
|
||||||
it.id === id && it.cdn === cdn && !it.tcpoOnly && !it.ipv6,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (found) return found
|
|
||||||
|
|
||||||
throw new Error(`Could not find${cdn ? ' CDN' : ''} DC ${id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change primary DC and write that fact to the storage.
|
* Change primary DC and write that fact to the storage.
|
||||||
* Will immediately reconnect to another DC.
|
* Will immediately reconnect to another DC.
|
||||||
|
@ -591,7 +520,12 @@ export class BaseTelegramClient extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
async changeDc(newDc: tl.RawDcOption | number): Promise<void> {
|
async changeDc(newDc: tl.RawDcOption | number): Promise<void> {
|
||||||
if (typeof newDc === 'number') {
|
if (typeof newDc === 'number') {
|
||||||
newDc = await this.getDcById(newDc)
|
const res = await this._config.findOption({
|
||||||
|
dcId: newDc,
|
||||||
|
allowIpv6: this._useIpv6,
|
||||||
|
})
|
||||||
|
if (!res) throw new Error('DC not found')
|
||||||
|
newDc = res
|
||||||
}
|
}
|
||||||
|
|
||||||
this._primaryDc = newDc
|
this._primaryDc = newDc
|
||||||
|
@ -764,7 +698,13 @@ export class BaseTelegramClient extends EventEmitter {
|
||||||
disableUpdates?: boolean
|
disableUpdates?: boolean
|
||||||
},
|
},
|
||||||
): Promise<SessionConnection> {
|
): Promise<SessionConnection> {
|
||||||
const dc = await this.getDcById(dcId, params?.media, params?.cdn)
|
const dc = await this._config.findOption({
|
||||||
|
dcId,
|
||||||
|
preferMedia: params?.media,
|
||||||
|
cdn: params?.cdn,
|
||||||
|
allowIpv6: this._useIpv6,
|
||||||
|
})
|
||||||
|
if (!dc) throw new Error('DC not found')
|
||||||
const connection = new SessionConnection(
|
const connection = new SessionConnection(
|
||||||
{
|
{
|
||||||
dc,
|
dc,
|
||||||
|
|
97
packages/core/src/network/config-manager.ts
Normal file
97
packages/core/src/network/config-manager.ts
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
import { tl } from '@mtcute/tl'
|
||||||
|
|
||||||
|
export class ConfigManager {
|
||||||
|
constructor(private _update: () => Promise<tl.RawConfig>) {}
|
||||||
|
|
||||||
|
private _destroyed = false
|
||||||
|
private _config?: tl.RawConfig
|
||||||
|
private _cdnConfig?: tl.RawCdnConfig
|
||||||
|
|
||||||
|
private _updateTimeout?: NodeJS.Timeout
|
||||||
|
private _updatingPromise?: Promise<void>
|
||||||
|
|
||||||
|
private _listeners: ((config: tl.RawConfig) => void)[] = []
|
||||||
|
|
||||||
|
get isStale(): boolean {
|
||||||
|
return !this._config || this._config.expires < Date.now() / 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
update(): Promise<void> {
|
||||||
|
if (!this.isStale) return Promise.resolve()
|
||||||
|
if (this._updatingPromise) return this._updatingPromise
|
||||||
|
|
||||||
|
return (this._updatingPromise = this._update().then((config) => {
|
||||||
|
if (this._destroyed) return
|
||||||
|
|
||||||
|
this._config = config
|
||||||
|
|
||||||
|
if (this._updateTimeout) clearTimeout(this._updateTimeout)
|
||||||
|
this._updateTimeout = setTimeout(
|
||||||
|
() => this.update(),
|
||||||
|
(config.expires - Date.now() / 1000) * 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const cb of this._listeners) cb(config)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
onConfigUpdate(cb: (config: tl.RawConfig) => void): void {
|
||||||
|
this._listeners.push(cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
offConfigUpdate(cb: (config: tl.RawConfig) => void): void {
|
||||||
|
const idx = this._listeners.indexOf(cb)
|
||||||
|
if (idx >= 0) this._listeners.splice(idx, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
getNow(): tl.RawConfig | undefined {
|
||||||
|
return this._config
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(): Promise<tl.RawConfig> {
|
||||||
|
if (this.isStale) await this.update()
|
||||||
|
return this._config!
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(): void {
|
||||||
|
if (this._updateTimeout) clearTimeout(this._updateTimeout)
|
||||||
|
this._listeners.length = 0
|
||||||
|
this._destroyed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOption(params: {
|
||||||
|
dcId: number
|
||||||
|
allowIpv6?: boolean
|
||||||
|
preferIpv6?: boolean
|
||||||
|
preferMedia?: boolean
|
||||||
|
cdn?: boolean
|
||||||
|
}): Promise<tl.RawDcOption | undefined> {
|
||||||
|
if (this.isStale) await this.update()
|
||||||
|
|
||||||
|
const options = this._config!.dcOptions.filter((opt) => {
|
||||||
|
if (opt.id === params.dcId) return true
|
||||||
|
if (opt.ipv6 && !params.allowIpv6) return false
|
||||||
|
if (opt.cdn && !params.cdn) return false
|
||||||
|
if (opt.tcpoOnly) return false // unsupported
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if (params.preferMedia && params.preferIpv6) {
|
||||||
|
const r = options.find((opt) => opt.mediaOnly && opt.ipv6)
|
||||||
|
if (r) return r
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.preferMedia) {
|
||||||
|
const r = options.find((opt) => opt.mediaOnly)
|
||||||
|
if (r) return r
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.preferIpv6) {
|
||||||
|
const r = options.find((opt) => opt.ipv6)
|
||||||
|
if (r) return r
|
||||||
|
}
|
||||||
|
|
||||||
|
return options[0]
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue