feat(core): qr code login
This commit is contained in:
parent
9daa551e33
commit
e2464f7f3f
13 changed files with 311 additions and 35 deletions
|
@ -743,6 +743,8 @@ withParams(params: RpcCallOptions): this\n`)
|
||||||
'computeSrpParams',
|
'computeSrpParams',
|
||||||
'computeNewPasswordHash',
|
'computeNewPasswordHash',
|
||||||
'onConnectionState',
|
'onConnectionState',
|
||||||
|
'getServerUpdateHandler',
|
||||||
|
'changePrimaryDc',
|
||||||
].forEach((name) => {
|
].forEach((name) => {
|
||||||
output.write(
|
output.write(
|
||||||
`TelegramClient.prototype.${name} = function(...args) {\n` +
|
`TelegramClient.prototype.${name} = function(...args) {\n` +
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {
|
||||||
writeStringSession,
|
writeStringSession,
|
||||||
} from '../utils/index.js'
|
} from '../utils/index.js'
|
||||||
import { LogManager } from '../utils/logger.js'
|
import { LogManager } from '../utils/logger.js'
|
||||||
import { ConnectionState, ITelegramClient } from './client.types.js'
|
import { ConnectionState, ITelegramClient, ServerUpdateHandler } from './client.types.js'
|
||||||
import { AppConfigManager } from './managers/app-config-manager.js'
|
import { AppConfigManager } from './managers/app-config-manager.js'
|
||||||
import { ITelegramStorageProvider } from './storage/provider.js'
|
import { ITelegramStorageProvider } from './storage/provider.js'
|
||||||
import { TelegramStorageManager, TelegramStorageManagerExtraOptions } from './storage/storage.js'
|
import { TelegramStorageManager, TelegramStorageManagerExtraOptions } from './storage/storage.js'
|
||||||
|
@ -30,7 +30,7 @@ export interface BaseTelegramClientOptions extends MtClientOptions {
|
||||||
|
|
||||||
export class BaseTelegramClient implements ITelegramClient {
|
export class BaseTelegramClient implements ITelegramClient {
|
||||||
readonly updates?: UpdatesManager
|
readonly updates?: UpdatesManager
|
||||||
private _serverUpdatesHandler: (updates: tl.TypeUpdates) => void = () => {}
|
private _serverUpdatesHandler: ServerUpdateHandler = () => {}
|
||||||
private _connectionStateHandler: (state: ConnectionState) => void = () => {}
|
private _connectionStateHandler: (state: ConnectionState) => void = () => {}
|
||||||
|
|
||||||
readonly log
|
readonly log
|
||||||
|
@ -276,10 +276,14 @@ export class BaseTelegramClient implements ITelegramClient {
|
||||||
this.updates?.handleClientUpdate(updates, noDispatch)
|
this.updates?.handleClientUpdate(updates, noDispatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
onServerUpdate(handler: (update: tl.TypeUpdates) => void): void {
|
onServerUpdate(handler: ServerUpdateHandler): void {
|
||||||
this._serverUpdatesHandler = handler
|
this._serverUpdatesHandler = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getServerUpdateHandler(): ServerUpdateHandler {
|
||||||
|
return this._serverUpdatesHandler
|
||||||
|
}
|
||||||
|
|
||||||
onUpdate(handler: RawUpdateHandler): void {
|
onUpdate(handler: RawUpdateHandler): void {
|
||||||
if (!this.updates) {
|
if (!this.updates) {
|
||||||
throw new MtArgumentError('Updates manager is disabled')
|
throw new MtArgumentError('Updates manager is disabled')
|
||||||
|
@ -325,4 +329,8 @@ export class BaseTelegramClient implements ITelegramClient {
|
||||||
get stopSignal(): AbortSignal {
|
get stopSignal(): AbortSignal {
|
||||||
return this.mt.stopSignal
|
return this.mt.stopSignal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changePrimaryDc(dcId: number): Promise<void> {
|
||||||
|
return this.mt.network.changePrimaryDc(dcId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { sendCode } from './methods/auth/send-code.js'
|
||||||
import { sendRecoveryCode } from './methods/auth/send-recovery-code.js'
|
import { sendRecoveryCode } from './methods/auth/send-recovery-code.js'
|
||||||
import { signIn } from './methods/auth/sign-in.js'
|
import { signIn } from './methods/auth/sign-in.js'
|
||||||
import { signInBot } from './methods/auth/sign-in-bot.js'
|
import { signInBot } from './methods/auth/sign-in-bot.js'
|
||||||
|
import { signInQr } from './methods/auth/sign-in-qr.js'
|
||||||
import { start } from './methods/auth/start.js'
|
import { start } from './methods/auth/start.js'
|
||||||
import { startTest } from './methods/auth/start-test.js'
|
import { startTest } from './methods/auth/start-test.js'
|
||||||
import { isSelfPeer } from './methods/auth/utils.js'
|
import { isSelfPeer } from './methods/auth/utils.js'
|
||||||
|
@ -262,6 +263,7 @@ import {
|
||||||
BotReactionCountUpdate,
|
BotReactionCountUpdate,
|
||||||
BotReactionUpdate,
|
BotReactionUpdate,
|
||||||
BotStoppedUpdate,
|
BotStoppedUpdate,
|
||||||
|
BusinessCallbackQuery,
|
||||||
BusinessChatLink,
|
BusinessChatLink,
|
||||||
BusinessConnection,
|
BusinessConnection,
|
||||||
BusinessMessage,
|
BusinessMessage,
|
||||||
|
@ -455,6 +457,13 @@ export interface TelegramClient extends ITelegramClient {
|
||||||
* @param handler Inline callback query handler
|
* @param handler Inline callback query handler
|
||||||
*/
|
*/
|
||||||
on(name: 'inline_callback_query', handler: (upd: InlineCallbackQuery) => void): this
|
on(name: 'inline_callback_query', handler: (upd: InlineCallbackQuery) => void): this
|
||||||
|
/**
|
||||||
|
* Register a business callback query handler
|
||||||
|
*
|
||||||
|
* @param name Event name
|
||||||
|
* @param handler Business callback query handler
|
||||||
|
*/
|
||||||
|
on(name: 'business_callback_query', handler: (upd: BusinessCallbackQuery) => void): this
|
||||||
/**
|
/**
|
||||||
* Register a poll update handler
|
* Register a poll update handler
|
||||||
*
|
*
|
||||||
|
@ -647,6 +656,9 @@ export interface TelegramClient extends ITelegramClient {
|
||||||
|
|
||||||
/** Confirmation code identifier from {@link SentCode} */
|
/** Confirmation code identifier from {@link SentCode} */
|
||||||
phoneCodeHash: string
|
phoneCodeHash: string
|
||||||
|
|
||||||
|
/** Abort signal */
|
||||||
|
abortSignal?: AbortSignal
|
||||||
}): Promise<SentCode>
|
}): Promise<SentCode>
|
||||||
/**
|
/**
|
||||||
* Simple wrapper that calls {@link start} and then
|
* Simple wrapper that calls {@link start} and then
|
||||||
|
@ -678,6 +690,9 @@ export interface TelegramClient extends ITelegramClient {
|
||||||
|
|
||||||
/** Additional code settings to pass to the server */
|
/** Additional code settings to pass to the server */
|
||||||
codeSettings?: Omit<tl.RawCodeSettings, '_' | 'logoutTokens'>
|
codeSettings?: Omit<tl.RawCodeSettings, '_' | 'logoutTokens'>
|
||||||
|
|
||||||
|
/** Abort signal */
|
||||||
|
abortSignal?: AbortSignal
|
||||||
}): Promise<SentCode>
|
}): Promise<SentCode>
|
||||||
/**
|
/**
|
||||||
* Send a code to email needed to recover your password
|
* Send a code to email needed to recover your password
|
||||||
|
@ -697,6 +712,29 @@ export interface TelegramClient extends ITelegramClient {
|
||||||
* @throws BadRequestError In case the bot token is invalid
|
* @throws BadRequestError In case the bot token is invalid
|
||||||
*/
|
*/
|
||||||
signInBot(token: string): Promise<User>
|
signInBot(token: string): Promise<User>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the [QR login flow](https://core.telegram.org/api/qr-login).
|
||||||
|
*
|
||||||
|
* This method will resolve once the authorization is complete,
|
||||||
|
* returning the authorized user.
|
||||||
|
* **Available**: 👤 users only
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
signInQr(params: {
|
||||||
|
/**
|
||||||
|
* Function that will be called whenever the login URL is changed.
|
||||||
|
*
|
||||||
|
* The app is expected to display `url` as a QR code to the user
|
||||||
|
*/
|
||||||
|
onUrlUpdated: (url: string, expires: Date) => void
|
||||||
|
|
||||||
|
/** Password for 2FA */
|
||||||
|
password?: MaybeDynamic<string>
|
||||||
|
|
||||||
|
/** Abort signal */
|
||||||
|
abortSignal?: AbortSignal
|
||||||
|
}): Promise<User>
|
||||||
/**
|
/**
|
||||||
* Authorize a user in Telegram with a valid confirmation code.
|
* Authorize a user in Telegram with a valid confirmation code.
|
||||||
*
|
*
|
||||||
|
@ -713,6 +751,8 @@ export interface TelegramClient extends ITelegramClient {
|
||||||
phoneCodeHash: string
|
phoneCodeHash: string
|
||||||
/** The confirmation code that was received */
|
/** The confirmation code that was received */
|
||||||
phoneCode: string
|
phoneCode: string
|
||||||
|
/** Abort signal */
|
||||||
|
abortSignal?: AbortSignal
|
||||||
}): Promise<User>
|
}): Promise<User>
|
||||||
/**
|
/**
|
||||||
* Utility function to quickly authorize on test DC
|
* Utility function to quickly authorize on test DC
|
||||||
|
@ -778,6 +818,15 @@ export interface TelegramClient extends ITelegramClient {
|
||||||
*/
|
*/
|
||||||
sessionForce?: boolean
|
sessionForce?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When passed, [QR login flow](https://core.telegram.org/api/qr-login)
|
||||||
|
* will be used instead of the regular login flow.
|
||||||
|
*
|
||||||
|
* This function will be called whenever the login URL is changed,
|
||||||
|
* and the app is expected to display it as a QR code to the user.
|
||||||
|
*/
|
||||||
|
qrCodeHandler?: (url: string, expires: Date) => void
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
@ -830,6 +879,9 @@ export interface TelegramClient extends ITelegramClient {
|
||||||
|
|
||||||
/** Additional code settings to pass to the server */
|
/** Additional code settings to pass to the server */
|
||||||
codeSettings?: Omit<tl.RawCodeSettings, '_' | 'logoutTokens'>
|
codeSettings?: Omit<tl.RawCodeSettings, '_' | 'logoutTokens'>
|
||||||
|
|
||||||
|
/** Abort signal */
|
||||||
|
abortSignal?: AbortSignal
|
||||||
}): Promise<User>
|
}): Promise<User>
|
||||||
/**
|
/**
|
||||||
* Check if the given peer/input peer is referring to the current user
|
* Check if the given peer/input peer is referring to the current user
|
||||||
|
@ -5613,6 +5665,9 @@ TelegramClient.prototype.sendRecoveryCode = function (...args) {
|
||||||
TelegramClient.prototype.signInBot = function (...args) {
|
TelegramClient.prototype.signInBot = function (...args) {
|
||||||
return signInBot(this._client, ...args)
|
return signInBot(this._client, ...args)
|
||||||
}
|
}
|
||||||
|
TelegramClient.prototype.signInQr = function (...args) {
|
||||||
|
return signInQr(this._client, ...args)
|
||||||
|
}
|
||||||
TelegramClient.prototype.signIn = function (...args) {
|
TelegramClient.prototype.signIn = function (...args) {
|
||||||
return signIn(this._client, ...args)
|
return signIn(this._client, ...args)
|
||||||
}
|
}
|
||||||
|
@ -6428,6 +6483,12 @@ TelegramClient.prototype.computeNewPasswordHash = function (...args) {
|
||||||
TelegramClient.prototype.onConnectionState = function (...args) {
|
TelegramClient.prototype.onConnectionState = function (...args) {
|
||||||
return this._client.onConnectionState(...args)
|
return this._client.onConnectionState(...args)
|
||||||
}
|
}
|
||||||
|
TelegramClient.prototype.getServerUpdateHandler = function (...args) {
|
||||||
|
return this._client.getServerUpdateHandler(...args)
|
||||||
|
}
|
||||||
|
TelegramClient.prototype.changePrimaryDc = function (...args) {
|
||||||
|
return this._client.changePrimaryDc(...args)
|
||||||
|
}
|
||||||
TelegramClient.prototype.onServerUpdate = function () {
|
TelegramClient.prototype.onServerUpdate = function () {
|
||||||
throw new Error('onServerUpdate is not available for TelegramClient, use .on() methods instead')
|
throw new Error('onServerUpdate is not available for TelegramClient, use .on() methods instead')
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@ import type { StringSessionData } from './utils/string-session.js'
|
||||||
*/
|
*/
|
||||||
export type ConnectionState = 'offline' | 'connecting' | 'updating' | 'connected'
|
export type ConnectionState = 'offline' | 'connecting' | 'updating' | 'connected'
|
||||||
|
|
||||||
|
export type ServerUpdateHandler = (update: tl.TypeUpdates) => void
|
||||||
|
|
||||||
// NB: when adding new methods, don't forget to add them to:
|
// NB: when adding new methods, don't forget to add them to:
|
||||||
// - worker/port.ts
|
// - worker/port.ts
|
||||||
// - generate-client script
|
// - generate-client script
|
||||||
|
@ -51,7 +53,8 @@ export interface ITelegramClient {
|
||||||
emitError(err: unknown): void
|
emitError(err: unknown): void
|
||||||
handleClientUpdate(updates: tl.TypeUpdates, noDispatch?: boolean): void
|
handleClientUpdate(updates: tl.TypeUpdates, noDispatch?: boolean): void
|
||||||
|
|
||||||
onServerUpdate(handler: (update: tl.TypeUpdates) => void): void
|
onServerUpdate(handler: ServerUpdateHandler): void
|
||||||
|
getServerUpdateHandler(): ServerUpdateHandler
|
||||||
onUpdate(handler: RawUpdateHandler): void
|
onUpdate(handler: RawUpdateHandler): void
|
||||||
onConnectionState(handler: (state: ConnectionState) => void): void
|
onConnectionState(handler: (state: ConnectionState) => void): void
|
||||||
|
|
||||||
|
@ -61,6 +64,7 @@ export interface ITelegramClient {
|
||||||
// or at least load this once at startup (and then these methods can be made sync)
|
// or at least load this once at startup (and then these methods can be made sync)
|
||||||
getPoolSize(kind: ConnectionKind, dcId?: number): Promise<number>
|
getPoolSize(kind: ConnectionKind, dcId?: number): Promise<number>
|
||||||
getPrimaryDcId(): Promise<number>
|
getPrimaryDcId(): Promise<number>
|
||||||
|
changePrimaryDc(newDc: number): Promise<void>
|
||||||
|
|
||||||
computeSrpParams(request: tl.account.RawPassword, password: string): Promise<tl.RawInputCheckPasswordSRP>
|
computeSrpParams(request: tl.account.RawPassword, password: string): Promise<tl.RawInputCheckPasswordSRP>
|
||||||
computeNewPasswordHash(algo: tl.TypePasswordKdfAlgo, password: string): Promise<Uint8Array>
|
computeNewPasswordHash(algo: tl.TypePasswordKdfAlgo, password: string): Promise<Uint8Array>
|
||||||
|
|
|
@ -10,6 +10,7 @@ export { sendCode } from './methods/auth/send-code.js'
|
||||||
export { sendRecoveryCode } from './methods/auth/send-recovery-code.js'
|
export { sendRecoveryCode } from './methods/auth/send-recovery-code.js'
|
||||||
export { signIn } from './methods/auth/sign-in.js'
|
export { signIn } from './methods/auth/sign-in.js'
|
||||||
export { signInBot } from './methods/auth/sign-in-bot.js'
|
export { signInBot } from './methods/auth/sign-in-bot.js'
|
||||||
|
export { signInQr } from './methods/auth/sign-in-qr.js'
|
||||||
export { start } from './methods/auth/start.js'
|
export { start } from './methods/auth/start.js'
|
||||||
export { startTest } from './methods/auth/start-test.js'
|
export { startTest } from './methods/auth/start-test.js'
|
||||||
export { isSelfPeer } from './methods/auth/utils.js'
|
export { isSelfPeer } from './methods/auth/utils.js'
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {
|
||||||
BotReactionCountUpdate,
|
BotReactionCountUpdate,
|
||||||
BotReactionUpdate,
|
BotReactionUpdate,
|
||||||
BotStoppedUpdate,
|
BotStoppedUpdate,
|
||||||
|
BusinessCallbackQuery,
|
||||||
BusinessChatLink,
|
BusinessChatLink,
|
||||||
BusinessConnection,
|
BusinessConnection,
|
||||||
BusinessMessage,
|
BusinessMessage,
|
||||||
|
|
|
@ -17,15 +17,21 @@ export async function resendCode(
|
||||||
|
|
||||||
/** Confirmation code identifier from {@link SentCode} */
|
/** Confirmation code identifier from {@link SentCode} */
|
||||||
phoneCodeHash: string
|
phoneCodeHash: string
|
||||||
|
|
||||||
|
/** Abort signal */
|
||||||
|
abortSignal?: AbortSignal
|
||||||
},
|
},
|
||||||
): Promise<SentCode> {
|
): Promise<SentCode> {
|
||||||
const { phone, phoneCodeHash } = params
|
const { phone, phoneCodeHash, abortSignal } = params
|
||||||
|
|
||||||
const res = await client.call({
|
const res = await client.call(
|
||||||
|
{
|
||||||
_: 'auth.resendCode',
|
_: 'auth.resendCode',
|
||||||
phoneNumber: normalizePhoneNumber(phone),
|
phoneNumber: normalizePhoneNumber(phone),
|
||||||
phoneCodeHash,
|
phoneCodeHash,
|
||||||
})
|
},
|
||||||
|
{ abortSignal },
|
||||||
|
)
|
||||||
|
|
||||||
assertTypeIs('sendCode', res, 'auth.sentCode')
|
assertTypeIs('sendCode', res, 'auth.sentCode')
|
||||||
|
|
||||||
|
|
|
@ -21,13 +21,17 @@ export async function sendCode(
|
||||||
|
|
||||||
/** Additional code settings to pass to the server */
|
/** Additional code settings to pass to the server */
|
||||||
codeSettings?: Omit<tl.RawCodeSettings, '_' | 'logoutTokens'>
|
codeSettings?: Omit<tl.RawCodeSettings, '_' | 'logoutTokens'>
|
||||||
|
|
||||||
|
/** Abort signal */
|
||||||
|
abortSignal?: AbortSignal
|
||||||
},
|
},
|
||||||
): Promise<SentCode> {
|
): Promise<SentCode> {
|
||||||
const phone = normalizePhoneNumber(params.phone)
|
const phone = normalizePhoneNumber(params.phone)
|
||||||
|
|
||||||
const { id, hash } = await client.getApiCrenetials()
|
const { id, hash } = await client.getApiCrenetials()
|
||||||
|
|
||||||
const res = await client.call({
|
const res = await client.call(
|
||||||
|
{
|
||||||
_: 'auth.sendCode',
|
_: 'auth.sendCode',
|
||||||
phoneNumber: phone,
|
phoneNumber: phone,
|
||||||
apiId: id,
|
apiId: id,
|
||||||
|
@ -37,7 +41,9 @@ export async function sendCode(
|
||||||
logoutTokens: params.futureAuthTokens,
|
logoutTokens: params.futureAuthTokens,
|
||||||
...params.codeSettings,
|
...params.codeSettings,
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
|
{ abortSignal: params.abortSignal },
|
||||||
|
)
|
||||||
|
|
||||||
assertTypeIs('sendCode', res, 'auth.sentCode')
|
assertTypeIs('sendCode', res, 'auth.sentCode')
|
||||||
|
|
||||||
|
|
141
packages/core/src/highlevel/methods/auth/sign-in-qr.ts
Normal file
141
packages/core/src/highlevel/methods/auth/sign-in-qr.ts
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
import { tl } from '@mtcute/tl'
|
||||||
|
|
||||||
|
import { getPlatform } from '../../../platform.js'
|
||||||
|
import { ControllablePromise, createControllablePromise } from '../../../utils/controllable-promise.js'
|
||||||
|
import { sleepWithAbort } from '../../../utils/misc-utils.js'
|
||||||
|
import { assertTypeIs } from '../../../utils/type-assertions.js'
|
||||||
|
import { ITelegramClient, ServerUpdateHandler } from '../../client.types.js'
|
||||||
|
import { MaybeDynamic, User } from '../../types/index.js'
|
||||||
|
import { resolveMaybeDynamic } from '../../utils/misc-utils.js'
|
||||||
|
import { checkPassword } from './check-password.js'
|
||||||
|
|
||||||
|
// @available=user
|
||||||
|
/**
|
||||||
|
* Execute the [QR login flow](https://core.telegram.org/api/qr-login).
|
||||||
|
*
|
||||||
|
* This method will resolve once the authorization is complete,
|
||||||
|
* returning the authorized user.
|
||||||
|
*/
|
||||||
|
export async function signInQr(
|
||||||
|
client: ITelegramClient,
|
||||||
|
params: {
|
||||||
|
/**
|
||||||
|
* Function that will be called whenever the login URL is changed.
|
||||||
|
*
|
||||||
|
* The app is expected to display `url` as a QR code to the user
|
||||||
|
*/
|
||||||
|
onUrlUpdated: (url: string, expires: Date) => void
|
||||||
|
|
||||||
|
/** Password for 2FA */
|
||||||
|
password?: MaybeDynamic<string>
|
||||||
|
|
||||||
|
/** Abort signal */
|
||||||
|
abortSignal?: AbortSignal
|
||||||
|
},
|
||||||
|
): Promise<User> {
|
||||||
|
const { onUrlUpdated, abortSignal } = params
|
||||||
|
|
||||||
|
let waiter: ControllablePromise<void> | undefined
|
||||||
|
|
||||||
|
// crutch – we need to wait for the updateLoginToken update.
|
||||||
|
// we replace the server update handler temporarily because:
|
||||||
|
// - updates manager may be disabled, in which case `onUpdate` will never be called
|
||||||
|
// - even if the updates manager is enabled, it won't start until we're logged in
|
||||||
|
//
|
||||||
|
// todo: how can we make this more clean?
|
||||||
|
const originalHandler = client.getServerUpdateHandler()
|
||||||
|
|
||||||
|
const onUpdate: ServerUpdateHandler = (upd) => {
|
||||||
|
if (upd._ === 'updateShort' && upd.update._ === 'updateLoginToken') {
|
||||||
|
waiter?.resolve()
|
||||||
|
client.onServerUpdate(originalHandler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.onServerUpdate(onUpdate)
|
||||||
|
|
||||||
|
abortSignal?.addEventListener('abort', () => {
|
||||||
|
client.onServerUpdate(originalHandler)
|
||||||
|
waiter?.reject(abortSignal.reason)
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { id, hash } = await client.getApiCrenetials()
|
||||||
|
const platform = getPlatform()
|
||||||
|
|
||||||
|
loop: while (true) {
|
||||||
|
let res: tl.auth.TypeLoginToken
|
||||||
|
|
||||||
|
try {
|
||||||
|
res = await client.call(
|
||||||
|
{
|
||||||
|
_: 'auth.exportLoginToken',
|
||||||
|
apiId: id,
|
||||||
|
apiHash: hash,
|
||||||
|
exceptIds: [],
|
||||||
|
},
|
||||||
|
{ abortSignal },
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
if (tl.RpcError.is(e, 'SESSION_PASSWORD_NEEDED') && params.password) {
|
||||||
|
return checkPassword(client, await resolveMaybeDynamic(params.password))
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (res._) {
|
||||||
|
case 'auth.loginToken':
|
||||||
|
onUrlUpdated(
|
||||||
|
`tg://login?token=${platform.base64Encode(res.token, true)}`,
|
||||||
|
new Date(res.expires * 1000),
|
||||||
|
)
|
||||||
|
|
||||||
|
waiter = createControllablePromise()
|
||||||
|
await Promise.race([waiter, sleepWithAbort(res.expires * 1000 - Date.now(), client.stopSignal)])
|
||||||
|
break
|
||||||
|
case 'auth.loginTokenMigrateTo': {
|
||||||
|
await client.changePrimaryDc(res.dcId)
|
||||||
|
|
||||||
|
let res2: tl.auth.TypeLoginToken
|
||||||
|
|
||||||
|
try {
|
||||||
|
res2 = await client.call(
|
||||||
|
{
|
||||||
|
_: 'auth.importLoginToken',
|
||||||
|
token: res.token,
|
||||||
|
},
|
||||||
|
{ abortSignal },
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
if (tl.RpcError.is(e, 'SESSION_PASSWORD_NEEDED') && params.password) {
|
||||||
|
return checkPassword(client, await resolveMaybeDynamic(params.password))
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTypeIs('auth.importLoginToken', res2, 'auth.loginTokenSuccess')
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
case 'auth.loginTokenSuccess':
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [self] = await client.call(
|
||||||
|
{
|
||||||
|
_: 'users.getUsers',
|
||||||
|
id: [{ _: 'inputUserSelf' }],
|
||||||
|
},
|
||||||
|
{ abortSignal },
|
||||||
|
)
|
||||||
|
assertTypeIs('users.getUsers', self, 'user')
|
||||||
|
|
||||||
|
await client.notifyLoggedIn(self)
|
||||||
|
|
||||||
|
return new User(self)
|
||||||
|
} finally {
|
||||||
|
client.onServerUpdate(originalHandler)
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,16 +19,21 @@ export async function signIn(
|
||||||
phoneCodeHash: string
|
phoneCodeHash: string
|
||||||
/** The confirmation code that was received */
|
/** The confirmation code that was received */
|
||||||
phoneCode: string
|
phoneCode: string
|
||||||
|
/** Abort signal */
|
||||||
|
abortSignal?: AbortSignal
|
||||||
},
|
},
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
const { phone, phoneCodeHash, phoneCode } = params
|
const { phone, phoneCodeHash, phoneCode, abortSignal } = params
|
||||||
|
|
||||||
const res = await client.call({
|
const res = await client.call(
|
||||||
|
{
|
||||||
_: 'auth.signIn',
|
_: 'auth.signIn',
|
||||||
phoneNumber: normalizePhoneNumber(phone),
|
phoneNumber: normalizePhoneNumber(phone),
|
||||||
phoneCodeHash,
|
phoneCodeHash,
|
||||||
phoneCode,
|
phoneCode,
|
||||||
})
|
},
|
||||||
|
{ abortSignal },
|
||||||
|
)
|
||||||
|
|
||||||
return _onAuthorization(client, res)
|
return _onAuthorization(client, res)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { resendCode } from './resend-code.js'
|
||||||
import { sendCode } from './send-code.js'
|
import { sendCode } from './send-code.js'
|
||||||
import { signIn } from './sign-in.js'
|
import { signIn } from './sign-in.js'
|
||||||
import { signInBot } from './sign-in-bot.js'
|
import { signInBot } from './sign-in-bot.js'
|
||||||
|
import { signInQr } from './sign-in-qr.js'
|
||||||
|
|
||||||
// @available=both
|
// @available=both
|
||||||
/**
|
/**
|
||||||
|
@ -48,6 +49,15 @@ export async function start(
|
||||||
*/
|
*/
|
||||||
sessionForce?: boolean
|
sessionForce?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When passed, [QR login flow](https://core.telegram.org/api/qr-login)
|
||||||
|
* will be used instead of the regular login flow.
|
||||||
|
*
|
||||||
|
* This function will be called whenever the login URL is changed,
|
||||||
|
* and the app is expected to display it as a QR code to the user.
|
||||||
|
*/
|
||||||
|
qrCodeHandler?: (url: string, expires: Date) => void
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
@ -100,12 +110,17 @@ export async function start(
|
||||||
|
|
||||||
/** Additional code settings to pass to the server */
|
/** Additional code settings to pass to the server */
|
||||||
codeSettings?: Omit<tl.RawCodeSettings, '_' | 'logoutTokens'>
|
codeSettings?: Omit<tl.RawCodeSettings, '_' | 'logoutTokens'>
|
||||||
|
|
||||||
|
/** Abort signal */
|
||||||
|
abortSignal?: AbortSignal
|
||||||
},
|
},
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
if (params.session) {
|
if (params.session) {
|
||||||
await client.importSession(params.session, params.sessionForce)
|
await client.importSession(params.session, params.sessionForce)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { abortSignal } = params
|
||||||
|
|
||||||
let has2fa = false
|
let has2fa = false
|
||||||
let sentCode: SentCode | undefined
|
let sentCode: SentCode | undefined
|
||||||
let phone: string | null = null
|
let phone: string | null = null
|
||||||
|
@ -128,7 +143,7 @@ export async function start(
|
||||||
}
|
}
|
||||||
|
|
||||||
// if has2fa == true, then we are half-logged in, but need to enter password
|
// if has2fa == true, then we are half-logged in, but need to enter password
|
||||||
if (!has2fa) {
|
if (!has2fa && !params.qrCodeHandler) {
|
||||||
if (!params.phone && !params.botToken) {
|
if (!params.phone && !params.botToken) {
|
||||||
throw new MtArgumentError('Neither phone nor bot token were provided')
|
throw new MtArgumentError('Neither phone nor bot token were provided')
|
||||||
}
|
}
|
||||||
|
@ -156,6 +171,7 @@ export async function start(
|
||||||
phone,
|
phone,
|
||||||
futureAuthTokens: params.futureAuthTokens,
|
futureAuthTokens: params.futureAuthTokens,
|
||||||
codeSettings: params.codeSettings,
|
codeSettings: params.codeSettings,
|
||||||
|
abortSignal,
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (tl.RpcError.is(e, 'SESSION_PASSWORD_NEEDED')) {
|
if (tl.RpcError.is(e, 'SESSION_PASSWORD_NEEDED')) {
|
||||||
|
@ -168,7 +184,11 @@ export async function start(
|
||||||
|
|
||||||
if (sentCode) {
|
if (sentCode) {
|
||||||
if (params.forceSms && (sentCode.type === 'app' || sentCode.type === 'email')) {
|
if (params.forceSms && (sentCode.type === 'app' || sentCode.type === 'email')) {
|
||||||
sentCode = await resendCode(client, { phone: phone!, phoneCodeHash: sentCode.phoneCodeHash })
|
sentCode = await resendCode(client, {
|
||||||
|
phone: phone!,
|
||||||
|
phoneCodeHash: sentCode.phoneCodeHash,
|
||||||
|
abortSignal,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.codeSentCallback) {
|
if (params.codeSentCallback) {
|
||||||
|
@ -186,7 +206,12 @@ export async function start(
|
||||||
if (!code) throw new tl.RpcError(400, 'PHONE_CODE_EMPTY')
|
if (!code) throw new tl.RpcError(400, 'PHONE_CODE_EMPTY')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await signIn(client, { phone: phone!, phoneCodeHash: sentCode.phoneCodeHash, phoneCode: code })
|
return await signIn(client, {
|
||||||
|
phone: phone!,
|
||||||
|
phoneCodeHash: sentCode.phoneCodeHash,
|
||||||
|
phoneCode: code,
|
||||||
|
abortSignal,
|
||||||
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!tl.RpcError.is(e)) throw e
|
if (!tl.RpcError.is(e)) throw e
|
||||||
|
|
||||||
|
@ -245,5 +270,13 @@ export async function start(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (params.qrCodeHandler) {
|
||||||
|
return await signInQr(client, {
|
||||||
|
onUrlUpdated: params.qrCodeHandler,
|
||||||
|
password: params.password,
|
||||||
|
abortSignal,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
throw new MtArgumentError('Failed to log in with provided credentials')
|
throw new MtArgumentError('Failed to log in with provided credentials')
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,6 +180,10 @@ export class UpdatesManager {
|
||||||
this._handler = handler
|
this._handler = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getHandler(): RawUpdateHandler {
|
||||||
|
return this._handler
|
||||||
|
}
|
||||||
|
|
||||||
onCatchingUp(handler: (catchingUp: boolean) => void): void {
|
onCatchingUp(handler: (catchingUp: boolean) => void): void {
|
||||||
this._onCatchingUp = handler
|
this._onCatchingUp = handler
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import { tl } from '@mtcute/tl'
|
|
||||||
|
|
||||||
import { LogManager } from '../../utils/logger.js'
|
import { LogManager } from '../../utils/logger.js'
|
||||||
import { ConnectionState, ITelegramClient } from '../client.types.js'
|
import { ConnectionState, ITelegramClient, ServerUpdateHandler } from '../client.types.js'
|
||||||
import { PeersIndex } from '../types/peers/peers-index.js'
|
import { PeersIndex } from '../types/peers/peers-index.js'
|
||||||
import { RawUpdateHandler } from '../updates/types.js'
|
import { RawUpdateHandler } from '../updates/types.js'
|
||||||
import { AppConfigManagerProxy } from './app-config.js'
|
import { AppConfigManagerProxy } from './app-config.js'
|
||||||
|
@ -37,6 +35,7 @@ export abstract class TelegramWorkerPort<Custom extends WorkerCustomMethods> imp
|
||||||
readonly getApiCrenetials
|
readonly getApiCrenetials
|
||||||
readonly getPoolSize
|
readonly getPoolSize
|
||||||
readonly getPrimaryDcId
|
readonly getPrimaryDcId
|
||||||
|
readonly changePrimaryDc
|
||||||
readonly computeSrpParams
|
readonly computeSrpParams
|
||||||
readonly computeNewPasswordHash
|
readonly computeNewPasswordHash
|
||||||
readonly startUpdatesLoop
|
readonly startUpdatesLoop
|
||||||
|
@ -71,6 +70,7 @@ export abstract class TelegramWorkerPort<Custom extends WorkerCustomMethods> imp
|
||||||
this.getApiCrenetials = bind('getApiCrenetials')
|
this.getApiCrenetials = bind('getApiCrenetials')
|
||||||
this.getPoolSize = bind('getPoolSize')
|
this.getPoolSize = bind('getPoolSize')
|
||||||
this.getPrimaryDcId = bind('getPrimaryDcId')
|
this.getPrimaryDcId = bind('getPrimaryDcId')
|
||||||
|
this.changePrimaryDc = bind('changePrimaryDc')
|
||||||
this.computeSrpParams = bind('computeSrpParams')
|
this.computeSrpParams = bind('computeSrpParams')
|
||||||
this.computeNewPasswordHash = bind('computeNewPasswordHash')
|
this.computeNewPasswordHash = bind('computeNewPasswordHash')
|
||||||
this.startUpdatesLoop = bind('startUpdatesLoop')
|
this.startUpdatesLoop = bind('startUpdatesLoop')
|
||||||
|
@ -79,11 +79,15 @@ export abstract class TelegramWorkerPort<Custom extends WorkerCustomMethods> imp
|
||||||
|
|
||||||
abstract connectToWorker(worker: SomeWorker, handler: ClientMessageHandler): [SendFn, () => void]
|
abstract connectToWorker(worker: SomeWorker, handler: ClientMessageHandler): [SendFn, () => void]
|
||||||
|
|
||||||
private _serverUpdatesHandler: (updates: tl.TypeUpdates) => void = () => {}
|
private _serverUpdatesHandler: ServerUpdateHandler = () => {}
|
||||||
onServerUpdate(handler: (updates: tl.TypeUpdates) => void): void {
|
onServerUpdate(handler: ServerUpdateHandler): void {
|
||||||
this._serverUpdatesHandler = handler
|
this._serverUpdatesHandler = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getServerUpdateHandler(): ServerUpdateHandler {
|
||||||
|
return this._serverUpdatesHandler
|
||||||
|
}
|
||||||
|
|
||||||
private _errorHandler: (err: unknown) => void = () => {}
|
private _errorHandler: (err: unknown) => void = () => {}
|
||||||
onError(handler: (err: unknown) => void): void {
|
onError(handler: (err: unknown) => void): void {
|
||||||
this._errorHandler = handler
|
this._errorHandler = handler
|
||||||
|
|
Loading…
Reference in a new issue