diff --git a/packages/core/src/highlevel/base.ts b/packages/core/src/highlevel/base.ts index 7ea50a87..6f97854d 100644 --- a/packages/core/src/highlevel/base.ts +++ b/packages/core/src/highlevel/base.ts @@ -229,7 +229,7 @@ export class BaseTelegramClient implements ITelegramClient { const authKey = await this.mt.storage.provider.authKeys.get(primaryDcs.main.id) if (!authKey) throw new Error('Auth key is not ready yet') - return writeStringSession(this.mt._writerMap, { + return writeStringSession({ version: 2, self: await this.storage.self.fetch(), testMode: Boolean(this.params.testMode), diff --git a/packages/core/src/highlevel/client.ts b/packages/core/src/highlevel/client.ts index 1774d822..2eefb85b 100644 --- a/packages/core/src/highlevel/client.ts +++ b/packages/core/src/highlevel/client.ts @@ -9,7 +9,6 @@ import { tl } from '@mtcute/tl' import { MemoryStorage } from '../storage/providers/memory/index.js' import { MaybeArray, MaybePromise, PartialExcept, PartialOnly } from '../types/index.js' -import { StringSessionData } from '../utils/string-session.js' import { BaseTelegramClient, BaseTelegramClientOptions } from './base.js' import { ITelegramClient } from './client.types.js' import { checkPassword } from './methods/auth/check-password.js' @@ -316,6 +315,7 @@ import { } from './types/index.js' import { makeParsedUpdateHandler, ParsedUpdateHandlerParams } from './updates/parsed.js' import { _defaultStorageFactory } from './utils/platform/storage.js' +import { StringSessionData } from './utils/string-session.js' // from methods/_init.ts // @copy diff --git a/packages/core/src/highlevel/client.types.ts b/packages/core/src/highlevel/client.types.ts index a1ec6ded..fb74c6bb 100644 --- a/packages/core/src/highlevel/client.types.ts +++ b/packages/core/src/highlevel/client.types.ts @@ -3,10 +3,10 @@ import { tl } from '@mtcute/tl' import type { ConnectionKind, RpcCallOptions } from '../network/index.js' import type { MustEqual, PublicPart } from '../types/utils.js' import type { Logger } from '../utils/logger.js' -import type { StringSessionData } from '../utils/string-session.js' import type { AppConfigManager } from './managers/app-config-manager.js' import type { TelegramStorageManager } from './storage/storage.js' import type { RawUpdateHandler } from './updates/types.js' +import type { StringSessionData } from './utils/string-session.js' // NB: when adding new methods, don't forget to add them to: // - worker/port.ts diff --git a/packages/core/src/highlevel/methods/_imports.ts b/packages/core/src/highlevel/methods/_imports.ts index 138d8da4..cfbd52d6 100644 --- a/packages/core/src/highlevel/methods/_imports.ts +++ b/packages/core/src/highlevel/methods/_imports.ts @@ -8,8 +8,6 @@ import { tl } from '@mtcute/tl' // @copy import { MaybeArray, MaybePromise, PartialExcept, PartialOnly } from '../../types/index.js' // @copy -import { StringSessionData } from '../../utils/string-session.js' -// @copy import { BaseTelegramClient, BaseTelegramClientOptions } from '../base.js' // @copy import { ITelegramClient } from '../client.types.js' @@ -94,3 +92,5 @@ import { UserStatusUpdate, UserTypingUpdate, } from '../types/index.js' +// @copy +import { StringSessionData } from '../utils/string-session.js' diff --git a/packages/core/src/highlevel/methods/auth/start.ts b/packages/core/src/highlevel/methods/auth/start.ts index 223ca918..0961cff1 100644 --- a/packages/core/src/highlevel/methods/auth/start.ts +++ b/packages/core/src/highlevel/methods/auth/start.ts @@ -3,12 +3,12 @@ import { tl } from '@mtcute/tl' import { MtArgumentError } from '../../../types/errors.js' import { MaybePromise } from '../../../types/utils.js' -import { StringSessionData } from '../../../utils/string-session.js' import { ITelegramClient } from '../../client.types.js' import { SentCode } from '../../types/auth/sent-code.js' import { User } from '../../types/peers/user.js' import { MaybeDynamic } from '../../types/utils.js' import { normalizePhoneNumber, resolveMaybeDynamic } from '../../utils/misc-utils.js' +import { StringSessionData } from '../../utils/string-session.js' import { getMe } from '../users/get-me.js' import { checkPassword } from './check-password.js' import { resendCode } from './resend-code.js' diff --git a/packages/core/src/highlevel/utils/index.ts b/packages/core/src/highlevel/utils/index.ts index 661c2378..705c5131 100644 --- a/packages/core/src/highlevel/utils/index.ts +++ b/packages/core/src/highlevel/utils/index.ts @@ -1,4 +1,4 @@ -// todo: merge this with the main utils dir +// todo: merge this with the main utils dir? export * from './file-utils.js' export * from './inline-utils.js' @@ -7,4 +7,5 @@ export * from './misc-utils.js' export * from './peer-utils.js' export * from './rps-meter.js' export * from './stream-utils.js' +export * from './string-session.js' export * from './voice-utils.js' diff --git a/packages/core/src/utils/string-session.test.ts b/packages/core/src/highlevel/utils/string-session.test.ts similarity index 56% rename from packages/core/src/utils/string-session.test.ts rename to packages/core/src/highlevel/utils/string-session.test.ts index b2f7faca..ce4492ed 100644 --- a/packages/core/src/utils/string-session.test.ts +++ b/packages/core/src/highlevel/utils/string-session.test.ts @@ -4,7 +4,7 @@ import { createStub } from '@mtcute/test' import { __tlReaderMap } from '@mtcute/tl/binary/reader.js' import { __tlWriterMap } from '@mtcute/tl/binary/writer.js' -import { defaultProductionDc } from './dcs.js' +import { defaultProductionDc } from '../../utils/dcs.js' import { readStringSession, writeStringSession } from './string-session.js' const stubAuthKey = new Uint8Array(32) @@ -12,43 +12,63 @@ const stubDcs = { main: createStub('dcOption', defaultProductionDc.main), media: createStub('dcOption', defaultProductionDc.media), } +const stubDcsBasic = { + main: { + id: 2, + ipAddress: defaultProductionDc.main.ipAddress, + ipv6: false, + mediaOnly: false, + port: 443, + }, + media: { + id: 2, + ipAddress: defaultProductionDc.media.ipAddress, + ipv6: false, + mediaOnly: true, + port: 443, + }, +} const stubDcsSameMedia = { main: stubDcs.main, media: stubDcs.main, } +const stubDcsBasicSameMedia = { + main: stubDcsBasic.main, + media: stubDcsBasic.main, +} describe('writeStringSession', () => { it('should write production string session without user', () => { expect( - writeStringSession(__tlWriterMap, { - version: 2, + writeStringSession({ + version: 3, testMode: false, - primaryDcs: stubDcs, + primaryDcs: stubDcsBasic, authKey: stubAuthKey, }), ).toMatchInlineSnapshot( - '"AgQAAAANobcYAAAAAAIAAAAOMTQ5LjE1NC4xNjcuNTAAuwEAAA2htxgCAAAAAgAAAA8xNDkuMTU0LjE2Ny4yMjK7AQAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"', + '"AwQAAAAXAQIADjE0OS4xNTQuMTY3LjUwALsBAAAXAQICDzE0OS4xNTQuMTY3LjIyMrsBAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"', ) }) it('should write production string session without user with same dc for media', () => { expect( - writeStringSession(__tlWriterMap, { - version: 2, + writeStringSession({ + version: 3, testMode: false, - primaryDcs: stubDcsSameMedia, + primaryDcs: stubDcsBasicSameMedia, authKey: stubAuthKey, }), ).toMatchInlineSnapshot( - '"AgAAAAANobcYAAAAAAIAAAAOMTQ5LjE1NC4xNjcuNTAAuwEAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"', + '"AwAAAAAXAQIADjE0OS4xNTQuMTY3LjUwALsBAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"', ) }) it('should write production string session with user', () => { expect( - writeStringSession(__tlWriterMap, { - version: 2, + writeStringSession({ + version: 3, testMode: false, - primaryDcs: stubDcs, + primaryDcs: stubDcsBasic, authKey: stubAuthKey, self: { userId: 12345, @@ -58,16 +78,16 @@ describe('writeStringSession', () => { }, }), ).toMatchInlineSnapshot( - '"AgUAAAANobcYAAAAAAIAAAAOMTQ5LjE1NC4xNjcuNTAAuwEAAA2htxgCAAAAAgAAAA8xNDkuMTU0LjE2Ny4yMjK7AQAAOTAAAAAAAAA3l3m8IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"', + '"AwUAAAAXAQIADjE0OS4xNTQuMTY3LjUwALsBAAAXAQICDzE0OS4xNTQuMTY3LjIyMrsBAAA5MAAAAAAAADeXebwgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"', ) }) it('should write test dc string session with user', () => { expect( - writeStringSession(__tlWriterMap, { - version: 2, + writeStringSession({ + version: 3, testMode: true, - primaryDcs: stubDcs, + primaryDcs: stubDcsBasic, authKey: stubAuthKey, self: { userId: 12345, @@ -77,12 +97,84 @@ describe('writeStringSession', () => { }, }), ).toMatchInlineSnapshot( - '"AgcAAAANobcYAAAAAAIAAAAOMTQ5LjE1NC4xNjcuNTAAuwEAAA2htxgCAAAAAgAAAA8xNDkuMTU0LjE2Ny4yMjK7AQAAOTAAAAAAAAA3l3m8IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"', + '"AwcAAAAXAQIADjE0OS4xNTQuMTY3LjUwALsBAAAXAQICDzE0OS4xNTQuMTY3LjIyMrsBAAA5MAAAAAAAADeXebwgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"', ) }) }) describe('readStringSession', () => { + describe('v3', () => { + it('should read production string session without user', () => { + expect( + readStringSession( + __tlReaderMap, + 'AwQAAAAXAQIADjE0OS4xNTQuMTY3LjUwALsBAAAXAQICDzE0OS4xNTQuMTY3LjIyMrsBAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + ), + ).toEqual({ + version: 3, + testMode: false, + primaryDcs: stubDcsBasic, + authKey: stubAuthKey, + self: null, + }) + }) + + it('should read production string session without user with same dc for media', () => { + expect( + readStringSession( + __tlReaderMap, + 'AwAAAAAXAQIADjE0OS4xNTQuMTY3LjUwALsBAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + ), + ).toEqual({ + version: 3, + testMode: false, + primaryDcs: stubDcsBasicSameMedia, + authKey: stubAuthKey, + self: null, + }) + }) + + it('should read production string session with user', () => { + expect( + readStringSession( + __tlReaderMap, + 'AwUAAAAXAQIADjE0OS4xNTQuMTY3LjUwALsBAAAXAQICDzE0OS4xNTQuMTY3LjIyMrsBAAA5MAAAAAAAADeXebwgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + ), + ).toEqual({ + version: 3, + testMode: false, + primaryDcs: stubDcsBasic, + authKey: stubAuthKey, + self: { + userId: 12345, + isBot: false, + isPremium: false, + usernames: [], + }, + }) + }) + + it('should read test dc string session with user', () => { + expect( + readStringSession( + __tlReaderMap, + 'AwcAAAAXAQIADjE0OS4xNTQuMTY3LjUwALsBAAAXAQICDzE0OS4xNTQuMTY3LjIyMrsBAAA5MAAAAAAAADeXebwgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + ), + ).toEqual({ + version: 3, + testMode: true, + primaryDcs: stubDcsBasic, + authKey: stubAuthKey, + self: { + userId: 12345, + isBot: false, + isPremium: false, + usernames: [], + }, + }) + }) + }) + describe('v2', () => { it('should read production string session without user', () => { expect( diff --git a/packages/core/src/utils/string-session.ts b/packages/core/src/highlevel/utils/string-session.ts similarity index 54% rename from packages/core/src/utils/string-session.ts rename to packages/core/src/highlevel/utils/string-session.ts index 614b7edb..acbff726 100644 --- a/packages/core/src/utils/string-session.ts +++ b/packages/core/src/highlevel/utils/string-session.ts @@ -1,17 +1,9 @@ -// todo move to highlevel/ import { tl } from '@mtcute/tl' -import { - base64DecodeToBuffer, - base64Encode, - TlBinaryReader, - TlBinaryWriter, - TlReaderMap, - TlWriterMap, -} from '@mtcute/tl-runtime' +import { base64DecodeToBuffer, base64Encode, TlBinaryReader, TlBinaryWriter, TlReaderMap } from '@mtcute/tl-runtime' -import { CurrentUserInfo } from '../highlevel/storage/service/current-user.js' -import { MtArgumentError } from '../types/index.js' -import { DcOptions } from './dcs.js' +import { MtArgumentError } from '../../types/index.js' +import { BasicDcOption, DcOptions, parseBasicDcOption, serializeBasicDcOption } from '../../utils/dcs.js' +import { CurrentUserInfo } from '../storage/service/current-user.js' export interface StringSessionData { version: number @@ -21,12 +13,12 @@ export interface StringSessionData { authKey: Uint8Array } -export function writeStringSession(writerMap: TlWriterMap, data: StringSessionData): string { - const writer = TlBinaryWriter.alloc(writerMap, 512) +export function writeStringSession(data: StringSessionData): string { + const writer = TlBinaryWriter.manual(512) const version = data.version - if (version !== 1 && version !== 2) { + if (version !== 3) { throw new MtArgumentError(`Unsupported string session version: ${version}`) } @@ -48,10 +40,10 @@ export function writeStringSession(writerMap: TlWriterMap, data: StringSessionDa } writer.int(flags) - writer.object(data.primaryDcs.main) + writer.bytes(serializeBasicDcOption(data.primaryDcs.main)) if (version >= 2 && data.primaryDcs.media !== data.primaryDcs.main) { - writer.object(data.primaryDcs.media) + writer.bytes(serializeBasicDcOption(data.primaryDcs.media)) } if (data.self) { @@ -80,11 +72,36 @@ export function readStringSession(readerMap: TlReaderMap, data: string): StringS const testMode = Boolean(flags & 2) const hasMedia = version >= 2 && Boolean(flags & 4) - const primaryDc = reader.object() as tl.TypeDcOption - const primaryMediaDc = hasMedia ? (reader.object() as tl.TypeDcOption) : primaryDc + let primaryDc: BasicDcOption + let primaryMediaDc: BasicDcOption - if (primaryDc._ !== 'dcOption') { - throw new MtArgumentError(`Invalid session string (dc._ = ${primaryDc._})`) + if (version <= 2) { + const primaryDc_ = reader.object() as tl.TypeDcOption + const primaryMediaDc_ = hasMedia ? (reader.object() as tl.TypeDcOption) : primaryDc_ + + if (primaryDc_._ !== 'dcOption') { + throw new MtArgumentError(`Invalid session string (dc._ = ${primaryDc_._})`) + } + + primaryDc = primaryDc_ + primaryMediaDc = primaryMediaDc_ + } else if (version === 3) { + const primaryDc_ = parseBasicDcOption(reader.bytes()) + + if (primaryDc_ === null) { + throw new MtArgumentError('Invalid session string (failed to parse primaryDc)') + } + + const primaryMediaDc_ = hasMedia ? parseBasicDcOption(reader.bytes()) : primaryDc_ + + if (primaryMediaDc_ === null) { + throw new MtArgumentError('Invalid session string (failed to parse primaryMediaDc)') + } + + primaryDc = primaryDc_ + primaryMediaDc = primaryMediaDc_ + } else { + throw new Error() // unreachable } let self: CurrentUserInfo | null = null diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 87b0e002..79ace5cd 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -19,7 +19,6 @@ export * from './misc-utils.js' export * from './peer-utils.js' export * from './platform/exit-hook.js' export * from './sorted-array.js' -export * from './string-session.js' export * from './tl-json.js' export * from './type-assertions.js' export * from '@mtcute/tl-runtime'