fix(core): session import/export

closes #17
This commit is contained in:
alina 🌸 2024-02-22 15:22:22 +03:00
parent 98eb01f664
commit 852bc10629
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
9 changed files with 155 additions and 46 deletions

View file

@ -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),

View file

@ -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

View file

@ -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

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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(

View file

@ -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

View file

@ -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'