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: {
|
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.
|
* Phone number of the account.
|
||||||
* If account does not exist, it will be created
|
* If account does not exist, it will be created
|
||||||
|
|
|
@ -5,7 +5,7 @@ interface AuthState {
|
||||||
// local copy of "self" in storage,
|
// local copy of "self" in storage,
|
||||||
// so we can use it w/out relying on storage.
|
// so we can use it w/out relying on storage.
|
||||||
// they are both loaded and saved to storage along with the updates
|
// they are both loaded and saved to storage along with the updates
|
||||||
// (see methods/updates/handle-update)
|
// (see methods/updates)
|
||||||
_userId: number | null
|
_userId: number | null
|
||||||
_isBot: boolean
|
_isBot: boolean
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,16 @@ import {
|
||||||
export async function start(
|
export async function start(
|
||||||
this: TelegramClient,
|
this: TelegramClient,
|
||||||
params: {
|
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.
|
* Phone number of the account.
|
||||||
* If account does not exist, it will be created
|
* If account does not exist, it will be created
|
||||||
|
@ -127,6 +137,10 @@ export async function start(
|
||||||
catchUp?: boolean
|
catchUp?: boolean
|
||||||
}
|
}
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
|
if (params.session) {
|
||||||
|
this.importSession(params.session)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const me = await this.getMe()
|
const me = await this.getMe()
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ interface UpdatesState {
|
||||||
_date: number
|
_date: number
|
||||||
_seq: 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
|
// used to avoid redundant storage calls
|
||||||
_oldPts: number
|
_oldPts: number
|
||||||
_oldDate: number
|
_oldDate: number
|
||||||
|
@ -106,9 +106,18 @@ export async function _loadStorage(this: TelegramClient): Promise<void> {
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @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
|
// 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 {
|
try {
|
||||||
// before any authorization pts will be undefined
|
// before any authorization pts will be undefined
|
||||||
if (this._pts !== undefined) {
|
if (this._pts !== undefined) {
|
||||||
|
|
|
@ -32,6 +32,9 @@ import { addPublicKey } from './utils/crypto/keys'
|
||||||
import { ITelegramStorage, MemoryStorage } from './storage'
|
import { ITelegramStorage, MemoryStorage } from './storage'
|
||||||
import { getAllPeersFrom, MAX_CHANNEL_ID } from './utils/peer-utils'
|
import { getAllPeersFrom, MAX_CHANNEL_ID } from './utils/peer-utils'
|
||||||
import bigInt from 'big-integer'
|
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')
|
const debug = require('debug')('mtcute:base')
|
||||||
|
|
||||||
|
@ -226,6 +229,8 @@ export class BaseTelegramClient {
|
||||||
*/
|
*/
|
||||||
primaryConnection: TelegramConnection
|
primaryConnection: TelegramConnection
|
||||||
|
|
||||||
|
private _importFrom?: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method which is called every time the client receives a new update.
|
* 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
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
protected _handleUpdate(update: tl.TypeUpdates): void {}
|
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) {
|
constructor(opts: BaseTelegramClient.Options) {
|
||||||
const apiId =
|
const apiId =
|
||||||
typeof opts.apiId === 'string' ? parseInt(opts.apiId) : opts.apiId
|
typeof opts.apiId === 'string' ? parseInt(opts.apiId) : opts.apiId
|
||||||
|
@ -291,7 +288,7 @@ export class BaseTelegramClient {
|
||||||
await this.storage.load?.()
|
await this.storage.load?.()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async _saveStorage(): Promise<void> {
|
protected async _saveStorage(afterImport = false): Promise<void> {
|
||||||
await this.storage.save?.()
|
await this.storage.save?.()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,6 +367,38 @@ export class BaseTelegramClient {
|
||||||
this.primaryConnection.authKey = await this.storage.getAuthKeyFor(
|
this.primaryConnection.authKey = await this.storage.getAuthKeyFor(
|
||||||
this._primaryDc.id
|
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()
|
this.primaryConnection.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -750,4 +779,64 @@ export class BaseTelegramClient {
|
||||||
|
|
||||||
return hadMin
|
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.
|
* update with the peers added, which might not be very efficient.
|
||||||
*/
|
*/
|
||||||
getFullPeerById(id: number): MaybeAsync<tl.TypeUser | tl.TypeChat | null>
|
getFullPeerById(id: number): MaybeAsync<tl.TypeUser | tl.TypeChat | null>
|
||||||
|
|
||||||
// TODO!
|
|
||||||
// exportToString(): MaybeAsync<string>
|
|
||||||
// importFromString(): MaybeAsync<void>
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue