fix: dont export everything on first connection

This commit is contained in:
alina 🌸 2023-09-18 03:16:29 +03:00
parent 55edbde3e7
commit 7bf63b2507
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
11 changed files with 256 additions and 140 deletions

View file

@ -76,12 +76,16 @@ export async function startTest(
} }
const id = parseInt(phone[5]) const id = parseInt(phone[5])
if (!availableDcs.find((dc) => dc.id === id)) { throw new MtArgumentError(`${phone} has invalid DC ID (${id})`) } if (!availableDcs.find((dc) => dc.id === id)) {
throw new MtArgumentError(`${phone} has invalid DC ID (${id})`)
}
} else { } else {
let dcId = this._defaultDc.id let dcId = this._defaultDcs.main.id
if (params.dcId) { if (params.dcId) {
if (!availableDcs.find((dc) => dc.id === params!.dcId)) { throw new MtArgumentError(`DC ID is invalid (${dcId})`) } if (!availableDcs.find((dc) => dc.id === params!.dcId)) {
throw new MtArgumentError(`DC ID is invalid (${dcId})`)
}
dcId = params.dcId dcId = params.dcId
} }

View file

@ -74,7 +74,7 @@ export async function* downloadAsIterable(
const isWeb = tl.isAnyInputWebFileLocation(location) const isWeb = tl.isAnyInputWebFileLocation(location)
// we will receive a FileMigrateError in case this is invalid // we will receive a FileMigrateError in case this is invalid
if (!dcId) dcId = this._defaultDc.id if (!dcId) dcId = this._defaultDcs.main.id
const partSizeKb = const partSizeKb =
params.partSize ?? (fileSize ? determinePartSize(fileSize) : 64) params.partSize ?? (fileSize ? determinePartSize(fileSize) : 64)

View file

@ -77,7 +77,7 @@ export interface BaseTelegramClientOptions {
* When session already contains primary DC, this parameter is ignored. * When session already contains primary DC, this parameter is ignored.
* Defaults to Production DC 2. * Defaults to Production DC 2.
*/ */
defaultDc?: tl.RawDcOption defaultDcs?: ITelegramStorage.DcOptions
/** /**
* Whether to connect to test servers. * Whether to connect to test servers.
@ -211,10 +211,10 @@ export class BaseTelegramClient extends EventEmitter {
protected readonly _testMode: boolean protected readonly _testMode: boolean
/** /**
* Primary DC taken from {@link BaseTelegramClientOptions.defaultDc}, * Primary DCs taken from {@link BaseTelegramClientOptions.defaultDcs},
* loaded from session or changed by other means (like redirecting). * loaded from session or changed by other means (like redirecting).
*/ */
protected _defaultDc: tl.RawDcOption protected _defaultDcs: ITelegramStorage.DcOptions
private _niceStacks: boolean private _niceStacks: boolean
readonly _layer: number readonly _layer: number
@ -264,7 +264,7 @@ export class BaseTelegramClient extends EventEmitter {
this._useIpv6 = Boolean(opts.useIpv6) this._useIpv6 = Boolean(opts.useIpv6)
this._testMode = Boolean(opts.testMode) this._testMode = Boolean(opts.testMode)
let dc = opts.defaultDc let dc = opts.defaultDcs
if (!dc) { if (!dc) {
if (this._testMode) { if (this._testMode) {
@ -276,7 +276,7 @@ export class BaseTelegramClient extends EventEmitter {
} }
} }
this._defaultDc = dc this._defaultDcs = dc
this._niceStacks = opts.niceStacks ?? true this._niceStacks = opts.niceStacks ?? true
this._layer = opts.overrideLayer ?? tl.LAYER this._layer = opts.overrideLayer ?? tl.LAYER
@ -348,11 +348,11 @@ export class BaseTelegramClient extends EventEmitter {
const promise = (this._connected = createControllablePromise()) const promise = (this._connected = createControllablePromise())
await this._loadStorage() await this._loadStorage()
const primaryDc = await this.storage.getDefaultDc() const primaryDc = await this.storage.getDefaultDcs()
if (primaryDc !== null) this._defaultDc = primaryDc if (primaryDc !== null) this._defaultDcs = primaryDc
const defaultDcAuthKey = await this.storage.getAuthKeyFor( const defaultDcAuthKey = await this.storage.getAuthKeyFor(
this._defaultDc.id, this._defaultDcs.main.id,
) )
if ((this._importForce || !defaultDcAuthKey) && this._importFrom) { if ((this._importForce || !defaultDcAuthKey) && this._importFrom) {
@ -369,21 +369,24 @@ export class BaseTelegramClient extends EventEmitter {
) )
} }
this._defaultDc = data.primaryDc this._defaultDcs = data.primaryDcs
await this.storage.setDefaultDc(data.primaryDc) await this.storage.setDefaultDcs(data.primaryDcs)
if (data.self) { if (data.self) {
await this.storage.setSelf(data.self) await this.storage.setSelf(data.self)
} }
// await this.primaryConnection.setupKeys(data.authKey) // await this.primaryConnection.setupKeys(data.authKey)
await this.storage.setAuthKeyFor(data.primaryDc.id, data.authKey) await this.storage.setAuthKeyFor(
data.primaryDcs.main.id,
data.authKey,
)
await this._saveStorage(true) await this._saveStorage(true)
} }
this.network this.network
.connect(this._defaultDc) .connect(this._defaultDcs)
.then(() => { .then(() => {
promise.resolve() promise.resolve()
this._connected = true this._connected = true
@ -574,17 +577,17 @@ export class BaseTelegramClient extends EventEmitter {
* > with [@BotFather](//t.me/botfather) * > with [@BotFather](//t.me/botfather)
*/ */
async exportSession(): Promise<string> { async exportSession(): Promise<string> {
const primaryDc = await this.storage.getDefaultDc() const primaryDcs = await this.storage.getDefaultDcs()
if (!primaryDc) throw new Error('No default DC set') if (!primaryDcs) throw new Error('No default DC set')
const authKey = await this.storage.getAuthKeyFor(primaryDc.id) const authKey = await this.storage.getAuthKeyFor(primaryDcs.main.id)
if (!authKey) throw new Error('Auth key is not ready yet') if (!authKey) throw new Error('Auth key is not ready yet')
return writeStringSession(this._writerMap, { return writeStringSession(this._writerMap, {
version: 1, version: 2,
self: await this.storage.getSelf(), self: await this.storage.getSelf(),
testMode: this._testMode, testMode: this._testMode,
primaryDc, primaryDcs,
authKey, authKey,
}) })
} }

View file

@ -155,6 +155,7 @@ export class MultiSessionConnection extends EventEmitter {
isMainConnection: this.params.isMainConnection && i === 0, isMainConnection: this.params.isMainConnection && i === 0,
withUpdates: withUpdates:
this.params.isMainConnection && this.params.isMainConnection &&
this.params.isMainDcConnection &&
!this.params.disableUpdates, !this.params.disableUpdates,
}, },
session, session,

View file

@ -164,7 +164,7 @@ export class DcConnectionManager {
crypto: this.manager.params.crypto, crypto: this.manager.params.crypto,
initConnection: this.manager._initConnectionParams, initConnection: this.manager._initConnectionParams,
transportFactory: this.manager._transportFactory, transportFactory: this.manager._transportFactory,
dc: this._dc, dc: this._dcs.media,
testMode: this.manager.params.testMode, testMode: this.manager.params.testMode,
reconnectionStrategy: this.manager._reconnectionStrategy, reconnectionStrategy: this.manager._reconnectionStrategy,
layer: this.manager.params.layer, layer: this.manager.params.layer,
@ -173,6 +173,7 @@ export class DcConnectionManager {
writerMap: this.manager.params.writerMap, writerMap: this.manager.params.writerMap,
usePfs: this.manager.params.usePfs, usePfs: this.manager.params.usePfs,
isMainConnection: false, isMainConnection: false,
isMainDcConnection: this.isPrimary,
inactivityTimeout: this.manager.params.inactivityTimeout ?? 60_000, inactivityTimeout: this.manager.params.inactivityTimeout ?? 60_000,
}) })
@ -184,7 +185,7 @@ export class DcConnectionManager {
this.__baseConnectionParams(), this.__baseConnectionParams(),
this.manager._connectionCount( this.manager._connectionCount(
'upload', 'upload',
this._dc.id, this.dcId,
this.manager.params.isPremium, this.manager.params.isPremium,
), ),
this._log, this._log,
@ -195,7 +196,7 @@ export class DcConnectionManager {
this.__baseConnectionParams(), this.__baseConnectionParams(),
this.manager._connectionCount( this.manager._connectionCount(
'download', 'download',
this._dc.id, this.dcId,
this.manager.params.isPremium, this.manager.params.isPremium,
), ),
this._log, this._log,
@ -206,7 +207,7 @@ export class DcConnectionManager {
this.__baseConnectionParams(), this.__baseConnectionParams(),
this.manager._connectionCount( this.manager._connectionCount(
'downloadSmall', 'downloadSmall',
this._dc.id, this.dcId,
this.manager.params.isPremium, this.manager.params.isPremium,
), ),
this._log, this._log,
@ -222,13 +223,14 @@ export class DcConnectionManager {
constructor( constructor(
readonly manager: NetworkManager, readonly manager: NetworkManager,
readonly dcId: number, readonly dcId: number,
readonly _dc: tl.RawDcOption, readonly _dcs: ITelegramStorage.DcOptions,
public isPrimary = false, public isPrimary = false,
) { ) {
this._log.prefix = `[DC ${dcId}] ` this._log.prefix = `[DC ${dcId}] `
const mainParams = this.__baseConnectionParams() const mainParams = this.__baseConnectionParams()
mainParams.isMainConnection = true mainParams.isMainConnection = true
mainParams.dc = _dcs.main
if (isPrimary) { if (isPrimary) {
mainParams.inactivityTimeout = undefined mainParams.inactivityTimeout = undefined
@ -361,17 +363,13 @@ export class DcConnectionManager {
setIsPremium(isPremium: boolean): void { setIsPremium(isPremium: boolean): void {
this.upload.setCount( this.upload.setCount(
this.manager._connectionCount('upload', this._dc.id, isPremium), this.manager._connectionCount('upload', this.dcId, isPremium),
) )
this.download.setCount( this.download.setCount(
this.manager._connectionCount('download', this._dc.id, isPremium), this.manager._connectionCount('download', this.dcId, isPremium),
) )
this.downloadSmall.setCount( this.downloadSmall.setCount(
this.manager._connectionCount( this.manager._connectionCount('downloadSmall', this.dcId, isPremium),
'downloadSmall',
this._dc.id,
isPremium,
),
) )
} }
@ -481,6 +479,33 @@ export class NetworkManager {
config.onConfigUpdate(this._onConfigChanged) config.onConfigUpdate(this._onConfigChanged)
} }
private async _findDcOptions(
dcId: number,
): Promise<ITelegramStorage.DcOptions> {
const main = await this.config.findOption({
dcId,
allowIpv6: this.params.useIpv6,
preferIpv6: this.params.useIpv6,
allowMedia: false,
cdn: false,
})
const media = await this.config.findOption({
dcId,
allowIpv6: this.params.useIpv6,
preferIpv6: this.params.useIpv6,
allowMedia: true,
preferMedia: true,
cdn: false,
})
if (!main || !media) {
throw new Error(`Could not find DC ${dcId}`)
}
return { main, media }
}
private _switchPrimaryDc(dc: DcConnectionManager) { private _switchPrimaryDc(dc: DcConnectionManager) {
if (this._primaryDc && this._primaryDc !== dc) { if (this._primaryDc && this._primaryDc !== dc) {
this._primaryDc.setIsPrimary(false) this._primaryDc.setIsPrimary(false)
@ -536,19 +561,9 @@ export class NetworkManager {
this._log.debug('creating new DC %d', dcId) this._log.debug('creating new DC %d', dcId)
try { try {
const dcOption = await this.config.findOption({ const dcOptions = await this._findDcOptions(dcId)
dcId,
allowIpv6: this.params.useIpv6,
preferIpv6: this.params.useIpv6,
allowMedia: true,
preferMedia: true,
cdn: false,
})
if (!dcOption) { const dc = new DcConnectionManager(this, dcId, dcOptions)
throw new Error(`Could not find DC ${dcId}`)
}
const dc = new DcConnectionManager(this, dcId, dcOption)
if (!(await dc.loadKeys())) { if (!(await dc.loadKeys())) {
dc.main.requestAuth() dc.main.requestAuth()
@ -567,20 +582,36 @@ export class NetworkManager {
/** /**
* Perform initial connection to the default DC * Perform initial connection to the default DC
* *
* @param defaultDc Default DC to connect to * @param defaultDcs Default DCs to connect to
*/ */
async connect(defaultDc: tl.RawDcOption): Promise<void> { async connect(defaultDcs: ITelegramStorage.DcOptions): Promise<void> {
if (this._dcConnections[defaultDc.id]) { if (defaultDcs.main.id !== defaultDcs.media.id) {
throw new Error('Default DCs must be the same')
}
if (this._dcConnections[defaultDcs.main.id]) {
// shouldn't happen // shouldn't happen
throw new Error('DC manager already exists') throw new Error('DC manager already exists')
} }
const dc = new DcConnectionManager(this, defaultDc.id, defaultDc) const dc = new DcConnectionManager(this, defaultDcs.main.id, defaultDcs)
this._dcConnections[defaultDc.id] = dc this._dcConnections[defaultDcs.main.id] = dc
await this._switchPrimaryDc(dc) await this._switchPrimaryDc(dc)
} }
private _pendingExports: Record<number, Promise<void>> = {}
private async _exportAuthTo(manager: DcConnectionManager): Promise<void> { private async _exportAuthTo(manager: DcConnectionManager): Promise<void> {
if (manager.dcId in this._pendingExports) {
this._log.debug('waiting for auth export to dc %d', manager.dcId)
return this._pendingExports[manager.dcId]
}
this._log.debug('exporting auth to dc %d', manager.dcId)
const promise = createControllablePromise<void>()
this._pendingExports[manager.dcId] = promise
try {
const auth = await this.call({ const auth = await this.call({
_: 'auth.exportAuthorization', _: 'auth.exportAuthorization',
dcId: manager.dcId, dcId: manager.dcId,
@ -600,23 +631,17 @@ export class NetworkManager {
`Unexpected response from auth.importAuthorization: ${res._}`, `Unexpected response from auth.importAuthorization: ${res._}`,
) )
} }
}
async exportAuth(): Promise<void> { promise.resolve()
const dcs: Record<number, number> = {} delete this._pendingExports[manager.dcId]
const config = await this.config.get() } catch (e) {
this._log.warn(
for (const dc of config.dcOptions) { 'failed to export auth to dc %d: %s',
if (dc.cdn) continue manager.dcId,
dcs[dc.id] = dc.id e,
} )
promise.reject(e)
for (const dc of Object.values(dcs)) { throw e
if (dc === this._primaryDc!.dcId) continue
this._log.debug('exporting auth for dc %d', dc)
const manager = await this._getOtherDc(dc)
await this._exportAuthTo(manager)
} }
} }
@ -628,6 +653,8 @@ export class NetworkManager {
}) })
} }
// future-proofing. should probably remove once the implementation is stable
// eslint-disable-next-line @typescript-eslint/require-await
async notifyLoggedIn(auth: tl.auth.TypeAuthorization): Promise<void> { async notifyLoggedIn(auth: tl.auth.TypeAuthorization): Promise<void> {
if ( if (
auth._ === 'auth.authorizationSignUpRequired' || auth._ === 'auth.authorizationSignUpRequired' ||
@ -642,7 +669,7 @@ export class NetworkManager {
this.setIsPremium(auth.user.premium!) this.setIsPremium(auth.user.premium!)
await this.exportAuth() // await this.exportAuth()
} }
resetSessions(): void { resetSessions(): void {
@ -664,27 +691,17 @@ export class NetworkManager {
async changePrimaryDc(newDc: number): Promise<void> { async changePrimaryDc(newDc: number): Promise<void> {
if (newDc === this._primaryDc?.dcId) return if (newDc === this._primaryDc?.dcId) return
const option = await this.config.findOption({ const options = await this._findDcOptions(newDc)
dcId: newDc,
allowIpv6: this.params.useIpv6,
preferIpv6: this.params.useIpv6,
cdn: false,
allowMedia: false,
})
if (!option) {
throw new Error(`DC ${newDc} not found`)
}
if (!this._dcConnections[newDc]) { if (!this._dcConnections[newDc]) {
this._dcConnections[newDc] = new DcConnectionManager( this._dcConnections[newDc] = new DcConnectionManager(
this, this,
newDc, newDc,
option, options,
) )
} }
await this._storage.setDefaultDc(option) await this._storage.setDefaultDcs(options)
await this._switchPrimaryDc(this._dcConnections[newDc]) await this._switchPrimaryDc(this._dcConnections[newDc])
} }

View file

@ -41,6 +41,7 @@ export interface SessionConnectionParams extends PersistentConnectionParams {
disableUpdates?: boolean disableUpdates?: boolean
withUpdates?: boolean withUpdates?: boolean
isMainConnection: boolean isMainConnection: boolean
isMainDcConnection: boolean
usePfs?: boolean usePfs?: boolean
readerMap: TlReaderMap readerMap: TlReaderMap

View file

@ -21,6 +21,11 @@ export namespace ITelegramStorage {
isBot: boolean isBot: boolean
userId: number userId: number
} }
export interface DcOptions {
main: tl.RawDcOption
media: tl.RawDcOption
}
} }
/** /**
@ -68,12 +73,12 @@ export interface ITelegramStorage {
/** /**
* Set default datacenter to use with this session. * Set default datacenter to use with this session.
*/ */
setDefaultDc(dc: tl.RawDcOption | null): MaybeAsync<void> setDefaultDcs(dcs: ITelegramStorage.DcOptions | null): MaybeAsync<void>
/** /**
* Get default datacenter for this session * Get default datacenter for this session
* (by default should return null) * (by default should return null)
*/ */
getDefaultDc(): MaybeAsync<tl.RawDcOption | null> getDefaultDcs(): MaybeAsync<ITelegramStorage.DcOptions | null>
/** /**
* Get auth_key for a given DC * Get auth_key for a given DC
@ -92,7 +97,12 @@ export interface ITelegramStorage {
* Set temp_auth_key for a given DC * Set temp_auth_key for a given DC
* expiresAt is unix time in ms * expiresAt is unix time in ms
*/ */
setTempAuthKeyFor(dcId: number, index: number, key: Buffer | null, expiresAt: number): MaybeAsync<void> setTempAuthKeyFor(
dcId: number,
index: number,
key: Buffer | null,
expiresAt: number
): MaybeAsync<void>
/** /**
* Remove all saved auth keys (both temp and perm) * Remove all saved auth keys (both temp and perm)
* for the given DC. Used when perm_key becomes invalid, * for the given DC. Used when perm_key becomes invalid,

View file

@ -13,7 +13,7 @@ export interface MemorySessionState {
// forwards compatibility for persistent storages // forwards compatibility for persistent storages
$version: typeof CURRENT_VERSION $version: typeof CURRENT_VERSION
defaultDc: tl.RawDcOption | null defaultDcs: ITelegramStorage.DcOptions | null
authKeys: Record<number, Buffer | null> authKeys: Record<number, Buffer | null>
authKeysTemp: Record<string, Buffer | null> authKeysTemp: Record<string, Buffer | null>
authKeysTempExpiry: Record<string, number> authKeysTempExpiry: Record<string, number>
@ -110,7 +110,7 @@ export class MemoryStorage implements ITelegramStorage, IStateStorage {
reset(): void { reset(): void {
this._state = { this._state = {
$version: CURRENT_VERSION, $version: CURRENT_VERSION,
defaultDc: null, defaultDcs: null,
authKeys: {}, authKeys: {},
authKeysTemp: {}, authKeysTemp: {},
authKeysTempExpiry: {}, authKeysTempExpiry: {},
@ -183,12 +183,12 @@ export class MemoryStorage implements ITelegramStorage, IStateStorage {
}) })
} }
getDefaultDc(): tl.RawDcOption | null { getDefaultDcs(): ITelegramStorage.DcOptions | null {
return this._state.defaultDc return this._state.defaultDcs
} }
setDefaultDc(dc: tl.RawDcOption | null): void { setDefaultDcs(dcs: ITelegramStorage.DcOptions | null): void {
this._state.defaultDc = dc this._state.defaultDcs = dcs
} }
setTempAuthKeyFor( setTempAuthKeyFor(

View file

@ -1,37 +1,73 @@
import { tl } from '@mtcute/tl' import { ITelegramStorage } from '../storage'
/** @internal */ /** @internal */
export const defaultProductionDc: tl.RawDcOption = { export const defaultProductionDc: ITelegramStorage.DcOptions = {
main: {
_: 'dcOption', _: 'dcOption',
ipAddress: '149.154.167.50', ipAddress: '149.154.167.50',
port: 443, port: 443,
id: 2, id: 2,
},
media: {
_: 'dcOption',
ipAddress: '149.154.167.222',
port: 443,
id: 2,
mediaOnly: true,
},
} }
/** @internal */ /** @internal */
export const defaultProductionIpv6Dc: tl.RawDcOption = { export const defaultProductionIpv6Dc: ITelegramStorage.DcOptions = {
main: {
_: 'dcOption', _: 'dcOption',
ipAddress: '2001:67c:4e8:f002::a', ipAddress: '2001:067c:04e8:f002:0000:0000:0000:000a',
ipv6: true, ipv6: true,
port: 443, port: 443,
id: 2, id: 2,
},
media: {
_: 'dcOption',
ipAddress: '2001:067c:04e8:f002:0000:0000:0000:000b',
ipv6: true,
mediaOnly: true,
port: 443,
id: 2,
},
} }
/** @internal */ /** @internal */
export const defaultTestDc: tl.RawDcOption = { export const defaultTestDc: ITelegramStorage.DcOptions = {
main: {
_: 'dcOption', _: 'dcOption',
ipAddress: '149.154.167.40', ipAddress: '149.154.167.40',
port: 443, port: 443,
id: 2, id: 2,
},
media: {
_: 'dcOption',
ipAddress: '149.154.167.40',
port: 443,
id: 2,
},
} }
/** @internal */ /** @internal */
export const defaultTestIpv6Dc: tl.RawDcOption = { export const defaultTestIpv6Dc: ITelegramStorage.DcOptions = {
main: {
_: 'dcOption', _: 'dcOption',
ipAddress: '2001:67c:4e8:f002::e', ipAddress: '2001:67c:4e8:f002::e',
port: 443, port: 443,
ipv6: true, ipv6: true,
id: 2, id: 2,
},
media: {
_: 'dcOption',
ipAddress: '2001:67c:4e8:f002::e',
port: 443,
ipv6: true,
id: 2,
},
} }
export const defaultDcs = { export const defaultDcs = {

View file

@ -1,5 +1,10 @@
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { TlBinaryReader, TlBinaryWriter, TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime' import {
TlBinaryReader,
TlBinaryWriter,
TlReaderMap,
TlWriterMap,
} from '@mtcute/tl-runtime'
import { ITelegramStorage } from '../storage' import { ITelegramStorage } from '../storage'
import { encodeUrlSafeBase64, parseUrlSafeBase64 } from './buffer-utils' import { encodeUrlSafeBase64, parseUrlSafeBase64 } from './buffer-utils'
@ -7,7 +12,7 @@ import { encodeUrlSafeBase64, parseUrlSafeBase64 } from './buffer-utils'
export interface StringSessionData { export interface StringSessionData {
version: number version: number
testMode: boolean testMode: boolean
primaryDc: tl.TypeDcOption primaryDcs: ITelegramStorage.DcOptions
self?: ITelegramStorage.SelfInfo | null self?: ITelegramStorage.SelfInfo | null
authKey: Buffer authKey: Buffer
} }
@ -20,7 +25,7 @@ export function writeStringSession(
const version = data.version const version = data.version
if (version !== 1) { if (version !== 1 && version !== 2) {
throw new Error(`Unsupported string session version: ${version}`) throw new Error(`Unsupported string session version: ${version}`)
} }
@ -38,7 +43,12 @@ export function writeStringSession(
writer.pos += 1 writer.pos += 1
writer.int(flags) writer.int(flags)
writer.object(data.primaryDc) writer.object(data.primaryDcs.main)
if (version >= 2 && data.primaryDcs.media !== data.primaryDcs.main) {
flags |= 4
writer.object(data.primaryDcs.media)
}
if (data.self) { if (data.self) {
writer.int53(data.self.userId) writer.int53(data.self.userId)
@ -50,23 +60,32 @@ export function writeStringSession(
return encodeUrlSafeBase64(writer.result()) return encodeUrlSafeBase64(writer.result())
} }
export function readStringSession(readerMap: TlReaderMap, data: string): StringSessionData { export function readStringSession(
readerMap: TlReaderMap,
data: string,
): StringSessionData {
const buf = parseUrlSafeBase64(data) const buf = parseUrlSafeBase64(data)
if (buf[0] !== 1) { throw new Error(`Invalid session string (version = ${buf[0]})`) } const version = buf[0]
if (version !== 1 && version !== 2) {
throw new Error(`Invalid session string (version = ${version})`)
}
const reader = new TlBinaryReader(readerMap, buf, 1) const reader = new TlBinaryReader(readerMap, buf, 1)
const flags = reader.int() const flags = reader.int()
const hasSelf = flags & 1 const hasSelf = flags & 1
const testMode = Boolean(flags & 2) const testMode = Boolean(flags & 2)
const hasMedia = version >= 2 && Boolean(flags & 4)
const primaryDc = reader.object() as tl.TypeDcOption const primaryDc = reader.object() as tl.TypeDcOption
const primaryMediaDc = hasMedia ?
(reader.object() as tl.TypeDcOption) :
primaryDc
if (primaryDc._ !== 'dcOption') { if (primaryDc._ !== 'dcOption') {
throw new Error( throw new Error(`Invalid session string (dc._ = ${primaryDc._})`)
`Invalid session string (dc._ = ${primaryDc._})`,
)
} }
let self: ITelegramStorage.SelfInfo | null = null let self: ITelegramStorage.SelfInfo | null = null
@ -86,7 +105,10 @@ export function readStringSession(readerMap: TlReaderMap, data: string): StringS
return { return {
version: 1, version: 1,
testMode, testMode,
primaryDc, primaryDcs: {
main: primaryDc,
media: primaryMediaDc,
},
self, self,
authKey: key, authKey: key,
} }

View file

@ -54,7 +54,7 @@ function getInputPeer(
throw new Error(`Invalid peer type: ${row.type}`) throw new Error(`Invalid peer type: ${row.type}`)
} }
const CURRENT_VERSION = 3 const CURRENT_VERSION = 4
// language=SQLite format=false // language=SQLite format=false
const TEMP_AUTH_TABLE = ` const TEMP_AUTH_TABLE = `
@ -392,6 +392,28 @@ export class SqliteStorage implements ITelegramStorage, IStateStorage {
from = 3 from = 3
} }
if (from === 3) {
// media dc support added
const oldDc = this._db
.prepare("select value from kv where key = 'def_dc'")
.get()
if (oldDc) {
const oldDcValue = JSON.parse(
(oldDc as { value: string }).value,
) as tl.RawDcOption
this._db
.prepare("update kv set value = ? where key = 'def_dc'")
.run([
JSON.stringify({
main: oldDcValue,
media: oldDcValue,
}),
])
}
from = 4
}
if (from !== CURRENT_VERSION) { if (from !== CURRENT_VERSION) {
// an assertion just in case i messed up // an assertion just in case i messed up
throw new Error('Migration incomplete') throw new Error('Migration incomplete')
@ -499,11 +521,11 @@ export class SqliteStorage implements ITelegramStorage, IStateStorage {
this._db.exec(RESET) this._db.exec(RESET)
} }
setDefaultDc(dc: tl.RawDcOption | null): void { setDefaultDcs(dc: ITelegramStorage.DcOptions | null): void {
return this._setToKv('def_dc', dc) return this._setToKv('def_dc', dc)
} }
getDefaultDc(): tl.RawDcOption | null { getDefaultDcs(): ITelegramStorage.DcOptions | null {
return this._getFromKv('def_dc') return this._getFromKv('def_dc')
} }