feat(client): string sessions
This commit is contained in:
parent
68ea4080df
commit
2cd443d6d1
6 changed files with 134 additions and 16 deletions
|
@ -377,6 +377,16 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
*
|
||||
*/
|
||||
start(params: {
|
||||
/**
|
||||
* String session exported using {@link TelegramClient.exportSession}.
|
||||
*
|
||||
* This simply calls {@link TelegramClient.importSession} before anything else.
|
||||
*
|
||||
* Note that passed session will be ignored in case storage already
|
||||
* contains authorization.
|
||||
*/
|
||||
session?: string
|
||||
|
||||
/**
|
||||
* Phone number of the account.
|
||||
* If account does not exist, it will be created
|
||||
|
|
|
@ -5,7 +5,7 @@ interface AuthState {
|
|||
// local copy of "self" in storage,
|
||||
// so we can use it w/out relying on storage.
|
||||
// they are both loaded and saved to storage along with the updates
|
||||
// (see methods/updates/handle-update)
|
||||
// (see methods/updates)
|
||||
_userId: number | null
|
||||
_isBot: boolean
|
||||
}
|
||||
|
|
|
@ -39,6 +39,16 @@ import {
|
|||
export async function start(
|
||||
this: TelegramClient,
|
||||
params: {
|
||||
/**
|
||||
* String session exported using {@link TelegramClient.exportSession}.
|
||||
*
|
||||
* This simply calls {@link TelegramClient.importSession} before anything else.
|
||||
*
|
||||
* Note that passed session will be ignored in case storage already
|
||||
* contains authorization.
|
||||
*/
|
||||
session?: string
|
||||
|
||||
/**
|
||||
* Phone number of the account.
|
||||
* If account does not exist, it will be created
|
||||
|
@ -127,6 +137,10 @@ export async function start(
|
|||
catchUp?: boolean
|
||||
}
|
||||
): Promise<User> {
|
||||
if (params.session) {
|
||||
this.importSession(params.session)
|
||||
}
|
||||
|
||||
try {
|
||||
const me = await this.getMe()
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ interface UpdatesState {
|
|||
_date: number
|
||||
_seq: number
|
||||
|
||||
// old values of the updates statej (i.e. as in DB)
|
||||
// old values of the updates state (i.e. as in DB)
|
||||
// used to avoid redundant storage calls
|
||||
_oldPts: number
|
||||
_oldDate: number
|
||||
|
@ -106,9 +106,18 @@ export async function _loadStorage(this: TelegramClient): Promise<void> {
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
export async function _saveStorage(this: TelegramClient): Promise<void> {
|
||||
export async function _saveStorage(this: TelegramClient, afterImport = false): Promise<void> {
|
||||
// save updates state to the session
|
||||
|
||||
if (afterImport) {
|
||||
// we need to get `self` from db and store it
|
||||
const self = await this.storage.getSelf()
|
||||
if (self) {
|
||||
this._userId = self.userId
|
||||
this._isBot = self.isBot
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// before any authorization pts will be undefined
|
||||
if (this._pts !== undefined) {
|
||||
|
|
|
@ -32,6 +32,9 @@ import { addPublicKey } from './utils/crypto/keys'
|
|||
import { ITelegramStorage, MemoryStorage } from './storage'
|
||||
import { getAllPeersFrom, MAX_CHANNEL_ID } from './utils/peer-utils'
|
||||
import bigInt from 'big-integer'
|
||||
import { BinaryWriter } from './utils/binary/binary-writer'
|
||||
import { encodeUrlSafeBase64, parseUrlSafeBase64 } from './utils/buffer-utils'
|
||||
import { BinaryReader } from './utils/binary/binary-reader'
|
||||
|
||||
const debug = require('debug')('mtcute:base')
|
||||
|
||||
|
@ -226,6 +229,8 @@ export class BaseTelegramClient {
|
|||
*/
|
||||
primaryConnection: TelegramConnection
|
||||
|
||||
private _importFrom?: string
|
||||
|
||||
/**
|
||||
* Method which is called every time the client receives a new update.
|
||||
*
|
||||
|
@ -236,14 +241,6 @@ export class BaseTelegramClient {
|
|||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
protected _handleUpdate(update: tl.TypeUpdates): void {}
|
||||
|
||||
/**
|
||||
* Method which is called for every object
|
||||
*
|
||||
* @param obj
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
protected _processApiResponse(obj: tl.TlObject): void {}
|
||||
|
||||
constructor(opts: BaseTelegramClient.Options) {
|
||||
const apiId =
|
||||
typeof opts.apiId === 'string' ? parseInt(opts.apiId) : opts.apiId
|
||||
|
@ -291,7 +288,7 @@ export class BaseTelegramClient {
|
|||
await this.storage.load?.()
|
||||
}
|
||||
|
||||
protected async _saveStorage(): Promise<void> {
|
||||
protected async _saveStorage(afterImport = false): Promise<void> {
|
||||
await this.storage.save?.()
|
||||
}
|
||||
|
||||
|
@ -370,6 +367,38 @@ export class BaseTelegramClient {
|
|||
this.primaryConnection.authKey = await this.storage.getAuthKeyFor(
|
||||
this._primaryDc.id
|
||||
)
|
||||
|
||||
if (!this.primaryConnection.authKey && this._importFrom) {
|
||||
const buf = parseUrlSafeBase64(this._importFrom)
|
||||
if (buf[0] !== 1) throw new Error(`Invalid session string (version = ${buf[0]})`)
|
||||
|
||||
const reader = new BinaryReader(buf, 1)
|
||||
|
||||
const flags = reader.int32()
|
||||
const hasSelf = flags & 1
|
||||
|
||||
const primaryDc = reader.object()
|
||||
if (primaryDc._ !== 'dcOption') {
|
||||
throw new Error(`Invalid session string (dc._ = ${primaryDc._})`)
|
||||
}
|
||||
|
||||
this._primaryDc = this.primaryConnection.params.dc = primaryDc
|
||||
await this.storage.setDefaultDc(primaryDc)
|
||||
|
||||
if (hasSelf) {
|
||||
const selfId = reader.int32()
|
||||
const selfBot = reader.boolean()
|
||||
|
||||
await this.storage.setSelf({ userId: selfId, isBot: selfBot })
|
||||
}
|
||||
|
||||
const key = reader.bytes()
|
||||
this.primaryConnection.authKey = key
|
||||
await this.storage.setAuthKeyFor(primaryDc.id, key)
|
||||
|
||||
await this._saveStorage(true)
|
||||
}
|
||||
|
||||
this.primaryConnection.connect()
|
||||
}
|
||||
|
||||
|
@ -750,4 +779,64 @@ export class BaseTelegramClient {
|
|||
|
||||
return hadMin
|
||||
}
|
||||
|
||||
/**
|
||||
* Export current session to a single *LONG* string, containing
|
||||
* all the needed information.
|
||||
*
|
||||
* > **Warning!** Anyone with this string will be able
|
||||
* > to authorize as you and do anything. Treat this
|
||||
* > as your password, and never give it away!
|
||||
* >
|
||||
* > In case you have accidentally leaked this string,
|
||||
* > make sure to revoke this session in account settings:
|
||||
* > "Privacy & Security" > "Active sessions" >
|
||||
* > find the one containing `mtcute` > Revoke,
|
||||
* > or, in case this is a bot, revoke bot token
|
||||
* > with [@BotFather](//t.me/botfather)
|
||||
*/
|
||||
async exportSession(): Promise<string> {
|
||||
if (!this.primaryConnection.authKey)
|
||||
throw new Error('Auth key is not generated yet')
|
||||
|
||||
const writer = BinaryWriter.alloc(512)
|
||||
|
||||
const self = await this.storage.getSelf()
|
||||
|
||||
const version = 1
|
||||
let flags = 0
|
||||
|
||||
if (self) {
|
||||
flags |= 1
|
||||
}
|
||||
|
||||
writer.buffer[0] = version
|
||||
writer.pos += 1
|
||||
|
||||
writer.int32(flags)
|
||||
writer.object(this._primaryDc)
|
||||
|
||||
if (self) {
|
||||
writer.int32(self.userId)
|
||||
writer.boolean(self.isBot)
|
||||
}
|
||||
|
||||
writer.bytes(this.primaryConnection.authKey)
|
||||
|
||||
return encodeUrlSafeBase64(writer.result())
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the session to be imported from the given session string.
|
||||
*
|
||||
* Note that the string will not be parsed and imported right away,
|
||||
* instead, it will be imported when `connect()` is called
|
||||
*
|
||||
* Also note that the session will only be imported in case
|
||||
* the storage is missing authorization (i.e. does not contain
|
||||
* auth key for the primary DC), otherwise it will be ignored.
|
||||
*/
|
||||
importSession(session: string): void {
|
||||
this._importFrom = session
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,8 +141,4 @@ export interface ITelegramStorage {
|
|||
* update with the peers added, which might not be very efficient.
|
||||
*/
|
||||
getFullPeerById(id: number): MaybeAsync<tl.TypeUser | tl.TypeChat | null>
|
||||
|
||||
// TODO!
|
||||
// exportToString(): MaybeAsync<string>
|
||||
// importFromString(): MaybeAsync<void>
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue