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 { readStringSession, writeStringSession } from './utils/string-session'
|
||||
|
||||
import { ConfigManager } from './network/config-manager'
|
||||
|
||||
export interface BaseTelegramClientOptions {
|
||||
/**
|
||||
* API ID from my.telegram.org
|
||||
|
@ -247,8 +249,9 @@ export class BaseTelegramClient extends EventEmitter {
|
|||
protected _lastUpdateTime = 0
|
||||
private _floodWaitedRequests: Record<string, number> = {}
|
||||
|
||||
protected _config?: tl.RawConfig
|
||||
protected _cdnConfig?: tl.RawCdnConfig
|
||||
protected _config = new ConfigManager(() =>
|
||||
this.call({ _: 'help.getConfig' })
|
||||
)
|
||||
|
||||
private _additionalConnections: SessionConnection[] = []
|
||||
|
||||
|
@ -499,6 +502,8 @@ export class BaseTelegramClient extends EventEmitter {
|
|||
async close(): Promise<void> {
|
||||
await this._onClose()
|
||||
|
||||
this._config.destroy()
|
||||
|
||||
this._cleanupPrimaryConnection(true)
|
||||
// close additional connections
|
||||
this._additionalConnections.forEach((conn) => conn.destroy())
|
||||
|
@ -507,82 +512,6 @@ export class BaseTelegramClient extends EventEmitter {
|
|||
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.
|
||||
* Will immediately reconnect to another DC.
|
||||
|
@ -591,7 +520,12 @@ export class BaseTelegramClient extends EventEmitter {
|
|||
*/
|
||||
async changeDc(newDc: tl.RawDcOption | number): Promise<void> {
|
||||
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
|
||||
|
@ -764,7 +698,13 @@ export class BaseTelegramClient extends EventEmitter {
|
|||
disableUpdates?: boolean
|
||||
},
|
||||
): 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(
|
||||
{
|
||||
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