chore!: moved away from global platform

This commit is contained in:
alina 🌸 2024-09-18 02:10:36 +03:00
parent 2ac7cbd35b
commit cb04b111a5
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
48 changed files with 166 additions and 211 deletions

View file

@ -11,19 +11,18 @@ import {
BaseTelegramClient as BaseTelegramClientBase,
TelegramClient as TelegramClientBase,
} from '@mtcute/core/client.js'
import { setPlatform } from '@mtcute/core/platform.js'
import { downloadToFile } from './methods/download-file.js'
import { downloadAsNodeStream } from './methods/download-node-stream.js'
import { BunPlatform } from './platform.js'
import { SqliteStorage } from './sqlite/index.js'
import { BunCryptoProvider } from './utils/crypto.js'
import { TcpTransport } from './utils/tcp.js'
import { BunPlatform } from './platform.js'
export type { TelegramClientOptions }
export interface BaseTelegramClientOptions
extends PartialOnly<Omit<BaseTelegramClientOptionsBase, 'storage'>, 'transport' | 'crypto'> {
extends PartialOnly<Omit<BaseTelegramClientOptionsBase, 'storage'>, 'transport' | 'crypto' | 'platform'> {
/**
* Storage to use for this client.
*
@ -33,23 +32,14 @@ export interface BaseTelegramClientOptions
* @default `"client.session"`
*/
storage?: string | ITelegramStorageProvider
/**
* **ADVANCED USE ONLY**
*
* Whether to not set up the platform.
* This is useful if you call `setPlatform` yourself.
*/
platformless?: boolean
}
export class BaseTelegramClient extends BaseTelegramClientBase {
constructor(opts: BaseTelegramClientOptions) {
if (!opts.platformless) setPlatform(new BunPlatform())
super({
crypto: new BunCryptoProvider(),
transport: TcpTransport,
platform: new BunPlatform(),
...opts,
storage:
typeof opts.storage === 'string'

View file

@ -1,6 +1,7 @@
import { afterAll, beforeAll, describe } from 'vitest'
import { LogManager } from '@mtcute/core/utils.js'
import {
defaultPlatform,
testAuthKeysRepository,
testKeyValueRepository,
testPeersRepository,
@ -14,7 +15,7 @@ if (import.meta.env.TEST_ENV === 'bun') {
const storage = new SqliteStorage(':memory:')
beforeAll(async () => {
storage.driver.setup(new LogManager())
storage.driver.setup(new LogManager(undefined, defaultPlatform), defaultPlatform)
await storage.driver.load()
})

View file

@ -1,13 +1,11 @@
import { Worker, parentPort } from 'node:worker_threads'
import { setPlatform } from '@mtcute/core/platform.js'
import type {
ClientMessageHandler,
RespondFn,
SendFn,
SomeWorker,
TelegramWorkerOptions,
TelegramWorkerPortOptions,
WorkerCustomMethods,
WorkerMessageHandler,
} from '@mtcute/core/worker.js'
@ -16,9 +14,13 @@ import {
TelegramWorkerPort as TelegramWorkerPortBase,
} from '@mtcute/core/worker.js'
import { BunPlatform } from './platform.js'
import { BunPlatform } from './platform'
export type { TelegramWorkerOptions, TelegramWorkerPortOptions, WorkerCustomMethods }
export type { TelegramWorkerOptions, WorkerCustomMethods }
export interface TelegramWorkerPortOptions {
worker: SomeWorker
}
let _registered = false
@ -45,9 +47,11 @@ export class TelegramWorker<T extends WorkerCustomMethods> extends TelegramWorke
}
export class TelegramWorkerPort<T extends WorkerCustomMethods> extends TelegramWorkerPortBase<T> {
constructor(readonly options: TelegramWorkerPortOptions) {
setPlatform(new BunPlatform())
super(options)
constructor(options: TelegramWorkerPortOptions) {
super({
worker: options.worker,
platform: new BunPlatform(),
})
}
connectToWorker(worker: SomeWorker, handler: ClientMessageHandler): [SendFn, () => void] {

View file

@ -14,7 +14,7 @@
"dependencies": {
"@mtcute/core": "workspace:^",
"@fuman/utils": "workspace:^",
"@fuman/ip": "workspace:^"
"@fuman/net": "workspace:^"
},
"devDependencies": {
"@mtcute/test": "workspace:^"

View file

@ -1,6 +1,6 @@
import { MtArgumentError } from '@mtcute/core'
import { base64, typed } from '@fuman/utils'
import { ip } from '@fuman/ip'
import { ip } from '@fuman/net'
import type { TelethonSession } from './types.js'

View file

@ -1,6 +1,6 @@
import { MtArgumentError } from '@mtcute/core'
import { base64, typed } from '@fuman/utils'
import { ip } from '@fuman/ip'
import { ip } from '@fuman/net'
import type { TelethonSession } from './types.js'

View file

@ -12,8 +12,7 @@
"./utils.js": "./src/utils/index.ts",
"./client.js": "./src/highlevel/client.ts",
"./worker.js": "./src/highlevel/worker/index.ts",
"./methods.js": "./src/highlevel/methods.ts",
"./platform.js": "./src/platform.ts"
"./methods.js": "./src/highlevel/methods.ts"
},
"scripts": {
"build": "pnpm run -w build-package core",

View file

@ -23,6 +23,7 @@ import {
writeStringSession,
} from '../utils/index.js'
import { LogManager } from '../utils/logger.js'
import type { ICorePlatform } from '../types/platform'
import type { ConnectionState, ITelegramClient, ServerUpdateHandler } from './client.types.js'
import { AppConfigManager } from './managers/app-config-manager.js'
@ -57,9 +58,11 @@ export class BaseTelegramClient implements ITelegramClient {
readonly mt: MtClient
readonly crypto: ICryptoProvider
readonly storage: TelegramStorageManager
readonly platform: ICorePlatform
constructor(readonly params: BaseTelegramClientOptions) {
this.log = this.params.logger ?? new LogManager('client')
this.log = this.params.logger ?? new LogManager('client', params.platform)
this.platform = this.params.platform
this.mt = new MtClient({
...this.params,
logger: this.log.create('mtproto'),
@ -238,11 +241,12 @@ export class BaseTelegramClient implements ITelegramClient {
if (defaultDcAuthKey && !force) return
const data = typeof session === 'string' ? readStringSession(session) : session
const testMode = data.primaryDcs.main.testMode
if (data.testMode && !this.params.testMode) {
if (testMode && !this.params.testMode) {
throw new Error(
'This session string is not for the current backend. '
+ `Session is ${data.testMode ? 'test' : 'prod'}, `
+ `Session is ${testMode ? 'test' : 'prod'}, `
+ `but the client is ${this.params.testMode ? 'test' : 'prod'}`,
)
}
@ -285,7 +289,6 @@ export class BaseTelegramClient implements ITelegramClient {
return writeStringSession({
version: 3,
self: await this.storage.self.fetch(),
testMode: Boolean(this.params.testMode),
primaryDcs,
authKey,
})

View file

@ -281,7 +281,7 @@ import { withParams } from './methods/misc/with-params.js'
// from methods/_init.ts
// @copy
type TelegramClientOptions = (
| (PartialOnly<Omit<BaseTelegramClientOptions, 'storage'>, 'transport' | 'crypto'> & {
| (PartialOnly<Omit<BaseTelegramClientOptions, 'storage'>, 'transport' | 'crypto' | 'platform'> & {
/**
* Storage to use for this client.
*

View file

@ -4,6 +4,7 @@ import type Long from 'long'
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 { ICorePlatform } from '../types/platform'
import type { AppConfigManager } from './managers/app-config-manager.js'
import type { TelegramStorageManager } from './storage/storage.js'
@ -35,6 +36,7 @@ export interface ITelegramClient {
readonly storage: PublicPart<TelegramStorageManager>
readonly appConfig: PublicPart<AppConfigManager>
readonly stopSignal: AbortSignal
readonly platform: ICorePlatform
prepare(): Promise<void>
connect(): Promise<void>

View file

@ -15,7 +15,7 @@ import { makeParsedUpdateHandler } from '../updates/parsed.js'
// @copy
type TelegramClientOptions = (
| (PartialOnly<Omit<BaseTelegramClientOptions, 'storage'>, 'transport' | 'crypto'> & {
| (PartialOnly<Omit<BaseTelegramClientOptions, 'storage'>, 'transport' | 'crypto' | 'platform'> & {
/**
* Storage to use for this client.
*

View file

@ -2,7 +2,6 @@ import Long from 'long'
import { parseFileId, tdFileId } from '@mtcute/file-id'
import { tl } from '@mtcute/tl'
import { getPlatform } from '../../../platform.js'
import { assertTypeIs } from '../../../utils/type-assertions.js'
import type { ITelegramClient } from '../../client.types.js'
import { isUploadedFile } from '../../types/files/uploaded-file.js'
@ -321,7 +320,7 @@ export async function _normalizeInputMedia(
} else if (typeof input === 'string' && input.match(/^file:/)) {
await upload(input.substring(5))
} else {
const parsed = typeof input === 'string' ? parseFileId(getPlatform(), input) : input
const parsed = typeof input === 'string' ? parseFileId(input) : input
if (parsed.location._ === 'photo') {
return {

View file

@ -3,7 +3,6 @@ import type { IReadable } from '@fuman/io'
import { read } from '@fuman/io'
import { AsyncLock } from '@fuman/utils'
import { getPlatform } from '../../../platform.js'
import { MtArgumentError } from '../../../types/errors.js'
import { randomLong } from '../../../utils/long-utils.js'
import type { ITelegramClient } from '../../client.types.js'
@ -111,10 +110,8 @@ export async function uploadFile(
let fileName = params.fileName
let fileMime = params.fileMime
const platform = getPlatform()
if (platform.normalizeFile) {
const res = await platform.normalizeFile(file)
if (client.platform.normalizeFile) {
const res = await client.platform.normalizeFile(file)
if (res?.file) {
file = res.file

View file

@ -1,7 +1,6 @@
import { tdFileId as td, toFileId, toUniqueFileId } from '@mtcute/file-id'
import type { tl } from '@mtcute/tl'
import { getPlatform } from '../../../platform.js'
import { makeInspectable } from '../../utils/index.js'
import { memoizeGetters } from '../../utils/memoize.js'
import { FileLocation } from '../files/index.js'
@ -125,7 +124,7 @@ export abstract class RawDocument extends FileLocation {
* representing this document.
*/
get fileId(): string {
return toFileId(getPlatform(), {
return toFileId({
type: this._fileIdType(),
dcId: this.raw.dcId,
fileReference: this.raw.fileReference,
@ -141,7 +140,7 @@ export abstract class RawDocument extends FileLocation {
* Get a unique File ID representing this document.
*/
get uniqueFileId(): string {
return toUniqueFileId(getPlatform(), td.FileType.Document, {
return toUniqueFileId(td.FileType.Document, {
_: 'common',
id: this.raw.id,
})

View file

@ -2,7 +2,6 @@ import Long from 'long'
import { tdFileId as td, toFileId, toUniqueFileId } from '@mtcute/file-id'
import type { tl } from '@mtcute/tl'
import { getPlatform } from '../../../platform.js'
import { MtArgumentError, MtTypeAssertionError } from '../../../types/errors.js'
import { assertTypeIs } from '../../../utils/type-assertions.js'
import { inflateSvgPath, strippedPhotoToJpg, svgPathToFile } from '../../utils/file-utils.js'
@ -204,7 +203,7 @@ export class Thumbnail extends FileLocation {
}
if (this._media._ === 'stickerSet') {
return toFileId(getPlatform(), {
return toFileId({
type: td.FileType.Thumbnail,
dcId: this.dcId!,
fileReference: null,
@ -222,7 +221,7 @@ export class Thumbnail extends FileLocation {
})
}
return toFileId(getPlatform(), {
return toFileId({
type: this._media._ === 'photo' ? td.FileType.Photo : td.FileType.Thumbnail,
dcId: this.dcId!,
fileReference: this._media.fileReference,
@ -251,7 +250,7 @@ export class Thumbnail extends FileLocation {
}
if (this._media._ === 'stickerSet') {
return toUniqueFileId(getPlatform(), td.FileType.Thumbnail, {
return toUniqueFileId(td.FileType.Thumbnail, {
_: 'photo',
id: Long.ZERO,
source: {
@ -263,7 +262,7 @@ export class Thumbnail extends FileLocation {
})
}
return toUniqueFileId(getPlatform(), this._media._ === 'photo' ? td.FileType.Photo : td.FileType.Thumbnail, {
return toUniqueFileId(this._media._ === 'photo' ? td.FileType.Photo : td.FileType.Thumbnail, {
_: 'photo',
id: this._media.id,
source: {

View file

@ -2,7 +2,6 @@ import Long from 'long'
import { tdFileId, toFileId, toUniqueFileId } from '@mtcute/file-id'
import type { tl } from '@mtcute/tl'
import { getPlatform } from '../../../platform.js'
import { MtArgumentError } from '../../../types/errors.js'
import { toggleChannelIdMark } from '../../../utils/peer-utils.js'
import { strippedPhotoToJpg } from '../../utils/file-utils.js'
@ -62,7 +61,7 @@ export class ChatPhotoSize extends FileLocation {
throw new MtArgumentError('Input peer was invalid')
}
return toFileId(getPlatform(), {
return toFileId({
dcId: this.obj.dcId,
type: tdFileId.FileType.ProfilePhoto,
fileReference: null,
@ -84,7 +83,7 @@ export class ChatPhotoSize extends FileLocation {
* TDLib and Bot API compatible unique File ID representing this size
*/
get uniqueFileId(): string {
return toUniqueFileId(getPlatform(), tdFileId.FileType.ProfilePhoto, {
return toUniqueFileId(tdFileId.FileType.ProfilePhoto, {
_: 'photo',
id: this.obj.photoId,
// eslint-disable-next-line ts/no-unsafe-assignment

View file

@ -3,7 +3,6 @@ import { parseFileId, tdFileId as td } from '@mtcute/file-id'
import type { tl } from '@mtcute/tl'
import { parseMarkedPeerId } from '../../utils/peer-utils.js'
import { getPlatform } from '../../platform.js'
import { assertNever } from '../../types/utils.js'
import FileType = td.FileType
@ -45,7 +44,7 @@ function dialogPhotoToInputPeer(
* @param fileId File ID, either parsed or as a string
*/
export function fileIdToInputWebFileLocation(fileId: string | FileId): tl.RawInputWebFileLocation {
if (typeof fileId === 'string') fileId = parseFileId(getPlatform(), fileId)
if (typeof fileId === 'string') fileId = parseFileId(fileId)
if (fileId.location._ !== 'web') {
throw new td.ConversionError('inputWebFileLocation')
@ -65,7 +64,7 @@ export function fileIdToInputWebFileLocation(fileId: string | FileId): tl.RawInp
* @param fileId File ID, either parsed or as a string
*/
export function fileIdToInputFileLocation(fileId: string | FileId): tl.TypeInputFileLocation {
if (typeof fileId === 'string') fileId = parseFileId(getPlatform(), fileId)
if (typeof fileId === 'string') fileId = parseFileId(fileId)
const loc = fileId.location
@ -219,7 +218,7 @@ export function fileIdToInputFileLocation(fileId: string | FileId): tl.TypeInput
* @param fileId File ID, either parsed or as a string
*/
export function fileIdToInputDocument(fileId: string | FileId): tl.RawInputDocument {
if (typeof fileId === 'string') fileId = parseFileId(getPlatform(), fileId)
if (typeof fileId === 'string') fileId = parseFileId(fileId)
if (
fileId.location._ !== 'common'
@ -256,7 +255,7 @@ export function fileIdToInputDocument(fileId: string | FileId): tl.RawInputDocum
* @param fileId File ID, either parsed or as a string
*/
export function fileIdToInputPhoto(fileId: string | FileId): tl.RawInputPhoto {
if (typeof fileId === 'string') fileId = parseFileId(getPlatform(), fileId)
if (typeof fileId === 'string') fileId = parseFileId(fileId)
if (fileId.location._ !== 'photo') {
throw new td.ConversionError('inputPhoto')
@ -281,7 +280,7 @@ export function fileIdToInputPhoto(fileId: string | FileId): tl.RawInputPhoto {
* @param fileId File ID, either parsed or as a string
*/
export function fileIdToEncryptedFile(fileId: string | FileId): tl.RawInputEncryptedFile {
if (typeof fileId === 'string') fileId = parseFileId(getPlatform(), fileId)
if (typeof fileId === 'string') fileId = parseFileId(fileId)
if (fileId.location._ !== 'common' || fileId.type !== FileType.Encrypted) {
throw new td.ConversionError('inputEncryptedFile')
@ -301,7 +300,7 @@ export function fileIdToEncryptedFile(fileId: string | FileId): tl.RawInputEncry
* @param fileId File ID, either parsed or as a string
*/
export function fileIdToSecureFile(fileId: string | FileId): tl.RawInputSecureFile {
if (typeof fileId === 'string') fileId = parseFileId(getPlatform(), fileId)
if (typeof fileId === 'string') fileId = parseFileId(fileId)
if (fileId.location._ !== 'common' || (fileId.type !== FileType.Secure && fileId.type !== FileType.SecureRaw)) {
throw new td.ConversionError('inputSecureFile')

View file

@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest'
import { StubTelegramClient } from '@mtcute/test'
import { sleep } from '@fuman/utils'
import { sleep } from '../../utils/misc-utils.js'
import type { ITelegramClient } from '../client.types.js'
import { batchedQuery } from './query-batcher.js'

View file

@ -6,6 +6,7 @@ import { LogManager } from '../../utils/logger.js'
import type { ConnectionState, ITelegramClient, ServerUpdateHandler } from '../client.types.js'
import { PeersIndex } from '../types/peers/peers-index.js'
import type { RawUpdateHandler } from '../updates/types.js'
import type { ICorePlatform } from '../../types/platform'
import { AppConfigManagerProxy } from './app-config.js'
import { WorkerInvoker } from './invoker.js'
@ -15,10 +16,12 @@ import { TelegramStorageProxy } from './storage.js'
export interface TelegramWorkerPortOptions {
worker: SomeWorker
platform: ICorePlatform
}
export abstract class TelegramWorkerPort<Custom extends WorkerCustomMethods> implements ITelegramClient {
readonly log: LogManager
readonly platform: ICorePlatform
private _connection
private _invoker
@ -51,7 +54,8 @@ export abstract class TelegramWorkerPort<Custom extends WorkerCustomMethods> imp
readonly stopSignal: AbortSignal = this._abortController.signal
constructor(readonly options: TelegramWorkerPortOptions) {
this.log = new LogManager('worker')
this.log = new LogManager('worker', options.platform)
this.platform = options.platform
this._connection = this.connectToWorker(this.options.worker, this._onMessage)
this._invoker = new WorkerInvoker(this._connection[0])

View file

@ -1,6 +1,6 @@
import Long from 'long'
import { describe, expect, it, vi } from 'vitest'
import { defaultTestCryptoProvider } from '@mtcute/test'
import { defaultPlatform, defaultTestCryptoProvider } from '@mtcute/test'
import type { TlBinaryReader, TlReaderMap } from '@mtcute/tl-runtime'
import { hex, utf8 } from '@fuman/utils'
@ -16,7 +16,7 @@ for (let i = 0; i < 256; i += 32) {
describe('AuthKey', () => {
async function create() {
const logger = new LogManager()
const logger = new LogManager(undefined, defaultPlatform)
const readerMap: TlReaderMap = {}
const crypto = await defaultTestCryptoProvider()

View file

@ -26,6 +26,7 @@ import {
defaultTestIpv6Dc,
isTlRpcError,
} from '../utils/index.js'
import type { ICorePlatform } from '../types/platform.js'
import { ConfigManager } from './config-manager.js'
import type { NetworkManagerExtraParams, RpcCallOptions } from './network-manager.js'
@ -57,6 +58,8 @@ export interface MtClientOptions {
*/
crypto: ICryptoProvider
platform: ICorePlatform
/**
* Whether to use IPv6 datacenters
* (IPv6 will be preferred when choosing a DC by id)
@ -223,7 +226,7 @@ export class MtClient extends EventEmitter {
constructor(readonly params: MtClientOptions) {
super()
this.log = params.logger ?? new LogManager()
this.log = params.logger ?? new LogManager(undefined, params.platform)
if (params.logLevel !== undefined) {
this.log.mgr.level = params.logLevel
@ -256,6 +259,7 @@ export class MtClient extends EventEmitter {
log: this.log,
readerMap: this._readerMap,
writerMap: this._writerMap,
platform: params.platform,
...params.storageOptions,
})
@ -282,6 +286,7 @@ export class MtClient extends EventEmitter {
onNetworkChanged: connected => this.emit('networkChanged', connected),
onUpdate: upd => this.emit('update', upd),
stopSignal: this.stopSignal,
platform: params.platform,
...params.network,
},
this._config,

View file

@ -4,13 +4,13 @@ import type Long from 'long'
import { type ReconnectionStrategy, defaultReconnectionStrategy } from '@fuman/net'
import { Deferred } from '@fuman/utils'
import { getPlatform } from '../platform.js'
import type { StorageManager } from '../storage/storage.js'
import { MtArgumentError, MtUnsupportedError, MtcuteError } from '../types/index.js'
import type { ComposedMiddleware, Middleware } from '../utils/composer.js'
import { composeMiddlewares } from '../utils/composer.js'
import type { DcOptions, ICryptoProvider, Logger } from '../utils/index.js'
import { assertTypeIs, isTlRpcError } from '../utils/type-assertions.js'
import type { ICorePlatform } from '../types/platform'
import type { ConfigManager } from './config-manager.js'
import { basic as defaultMiddlewares } from './middlewares/default.js'
@ -29,6 +29,7 @@ export interface NetworkManagerParams {
storage: StorageManager
crypto: ICryptoProvider
log: Logger
platform: ICorePlatform
enableErrorReporting: boolean
apiId: number
@ -253,6 +254,7 @@ export class DcConnectionManager {
inactivityTimeout: this.manager.params.inactivityTimeout ?? 60_000,
enableErrorReporting: this.manager.params.enableErrorReporting,
salts: this._salts,
platform: this.manager.params.platform,
})
const mainParams = baseConnectionParams()
@ -485,7 +487,7 @@ export class NetworkManager {
readonly params: NetworkManagerParams & NetworkManagerExtraParams,
readonly config: ConfigManager,
) {
const deviceModel = `mtcute on ${getPlatform().getDeviceModel()}`
const deviceModel = `mtcute on ${params.platform.getDeviceModel()}`
this._initConnectionParams = {
_: 'initConnection',
@ -616,7 +618,7 @@ export class NetworkManager {
throw new MtArgumentError('DC manager already exists')
}
this._resetOnNetworkChange = getPlatform().onNetworkChanged?.(this.notifyNetworkChanged.bind(this))
this._resetOnNetworkChange = this.params.platform.onNetworkChanged?.(this.notifyNetworkChanged.bind(this))
const dc = new DcConnectionManager(this, defaultDcs.main.id, defaultDcs, true)
this._dcConnections.set(defaultDcs.main.id, dc)

View file

@ -149,6 +149,8 @@ export abstract class PersistentConnection extends EventEmitter {
private async _onError(err: Error) {
this._updateLogPrefix()
this.onError(err)
return 'reconnect' as const
}
async changeTransport(transport: TelegramTransport): Promise<void> {

View file

@ -5,7 +5,6 @@ import type { TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime'
import { TlBinaryReader, TlBinaryWriter, TlSerializationCounter } from '@mtcute/tl-runtime'
import { Deferred, u8 } from '@fuman/utils'
import { getPlatform } from '../platform.js'
import { MtArgumentError, MtTimeoutError, MtcuteError } from '../types/index.js'
import { createAesIgeForMessageOld } from '../utils/crypto/mtproto.js'
import type { ICryptoProvider } from '../utils/index.js'
@ -16,6 +15,7 @@ import {
removeFromLongArray,
timers,
} from '../utils/index.js'
import type { ICorePlatform } from '../types/platform'
import { doAuthorization } from './authorization.js'
import type { MtprotoSession, PendingMessage, PendingRpc } from './mtproto-session.js'
@ -39,6 +39,7 @@ export interface SessionConnectionParams extends PersistentConnectionParams {
readerMap: TlReaderMap
writerMap: TlWriterMap
platform: ICorePlatform
}
const TEMP_AUTH_KEY_EXPIRY = 86400 // 24 hours
@ -97,7 +98,7 @@ export class SessionConnection extends PersistentConnection {
this._handleRawMessage = this._handleRawMessage.bind(this)
this._usePfs = this.params.usePfs ?? false
this._online = getPlatform().isOnline?.() ?? true
this._online = params.platform.isOnline?.() ?? true
}
private _online

View file

@ -1,23 +1,20 @@
import { describe, expect, it, vi } from 'vitest'
import { defaultTestCryptoProvider } from '@mtcute/test'
import { defaultPlatform, defaultTestCryptoProvider } from '@mtcute/test'
import { Bytes } from '@fuman/io'
import { hex } from '@fuman/utils'
import { getPlatform } from '../../platform.js'
import { LogManager } from '../../utils/index.js'
import { IntermediatePacketCodec } from './intermediate.js'
import type { MtProxyInfo } from './obfuscated.js'
import { ObfuscatedPacketCodec } from './obfuscated.js'
import { TransportError } from './abstract'
const p = getPlatform()
import { TransportError } from './abstract.js'
describe('ObfuscatedPacketCodec', () => {
const create = async (randomSource?: string, proxy?: MtProxyInfo) => {
const codec = new ObfuscatedPacketCodec(new IntermediatePacketCodec(), proxy)
const crypto = await defaultTestCryptoProvider(randomSource)
codec.setup(crypto, new LogManager())
codec.setup(crypto, new LogManager(undefined, defaultPlatform))
return [codec, crypto] as const
}
@ -191,7 +188,7 @@ describe('ObfuscatedPacketCodec', () => {
const spyInnerReset = vi.spyOn(inner, 'reset')
const codec = new ObfuscatedPacketCodec(inner)
codec.setup(await defaultTestCryptoProvider(), new LogManager())
codec.setup(await defaultTestCryptoProvider(), new LogManager(undefined, defaultPlatform))
await codec.tag()

View file

@ -1,49 +0,0 @@
import type { UploadFileLike } from './highlevel/types/files/utils.js'
import { MtUnsupportedError } from './types/errors.js'
import type { MaybePromise } from './types/index.js'
// todo: can we make this non-global?
export interface ICorePlatform {
beforeExit: (fn: () => void) => () => void
log: (color: number, level: number, tag: string, fmt: string, args: unknown[]) => void
getDefaultLogLevel: () => number | null
getDeviceModel: () => string
normalizeFile?: (file: UploadFileLike) => MaybePromise<{
file?: UploadFileLike
fileSize?: number
fileName?: string
} | null>
onNetworkChanged?: (fn: (connected: boolean) => void) => () => void
isOnline?: () => boolean
}
// NB: when using with some bundlers (e.g. vite) re-importing this module will not return the same object
// so we need to store the platform in a global object to be able to survive hot-reloads etc.
// try to use Symbol if available, otherwise fallback to a string
const platformKey = typeof Symbol !== 'undefined' ? Symbol.for('mtcute.platform') : '__MTCUTE_PLATFORM__'
// eslint-disable-next-line
let _platform: ICorePlatform | null = (globalThis as any)?.[platformKey] ?? null
export function setPlatform(platform: ICorePlatform): void {
if (_platform) {
if (_platform.constructor !== platform.constructor) {
throw new MtUnsupportedError('Platform may not be changed at runtime!')
}
return
}
_platform = platform
;(globalThis as any)[platformKey] = platform
}
export function getPlatform(): ICorePlatform {
if (!_platform) {
throw new MtUnsupportedError('Platform is not set! Have you instantiated the client?')
}
return _platform
}

View file

@ -1,3 +1,4 @@
import type { ICorePlatform } from '../types/platform'
import type { MaybePromise } from '../types/utils.js'
import type { Logger } from '../utils/logger.js'
@ -37,7 +38,7 @@ export interface IStorageDriver {
* Setup the driver, passing the logger instance,
* in case your driver needs it
*/
setup?: (log: Logger) => void
setup?: (log: Logger, platform: ICorePlatform) => void
}
/**
@ -53,9 +54,11 @@ export abstract class BaseStorageDriver implements IStorageDriver {
private _destroyed = false
protected _log!: Logger
protected _platform!: ICorePlatform
setup(log: Logger): void {
setup(log: Logger, platform: ICorePlatform): void {
this._log = log.create('sqlite')
this._platform = platform
}
protected get loaded(): boolean {

View file

@ -1,5 +1,6 @@
import { __tlReaderMap } from '@mtcute/tl/binary/reader.js'
import { __tlWriterMap } from '@mtcute/tl/binary/writer.js'
import { defaultPlatform } from '@mtcute/test'
import { LogManager } from '../../utils/logger.js'
import { MemoryStorageDriver } from '../memory/driver.js'
@ -7,7 +8,7 @@ import { MemoryStorageDriver } from '../memory/driver.js'
import type { ServiceOptions } from './base.js'
export function testServiceOptions(): ServiceOptions {
const logger = new LogManager()
const logger = new LogManager(undefined, defaultPlatform)
logger.level = 0
return {

View file

@ -1,4 +1,3 @@
import { getPlatform } from '../../platform.js'
import { BaseStorageDriver } from '../driver.js'
import type { ISqliteDatabase, ISqliteStatement } from './types.js'
@ -136,7 +135,7 @@ export abstract class BaseSqliteStorageDriver extends BaseStorageDriver {
this.db.transaction(() => this._initialize())()
this._cleanup = getPlatform().beforeExit(() => {
this._cleanup = this._platform.beforeExit(() => {
this._save()
this._destroy()
})

View file

@ -1,8 +1,8 @@
import type { TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime'
import { getPlatform } from '../platform.js'
import { asyncResettable } from '../utils/index.js'
import type { Logger } from '../utils/logger.js'
import type { ICorePlatform } from '../types/platform'
import type { IMtStorageProvider } from './provider.js'
import { AuthKeysService } from './service/auth-keys.js'
@ -13,6 +13,7 @@ import type { IStorageDriver } from './driver.js'
interface StorageManagerOptions {
provider: IMtStorageProvider
platform: ICorePlatform
log: Logger
readerMap: TlReaderMap
writerMap: TlWriterMap
@ -44,6 +45,7 @@ export interface StorageManagerExtraOptions {
export class StorageManager {
readonly provider: IMtStorageProvider
readonly driver: IStorageDriver
readonly platform: ICorePlatform
readonly log: Logger
readonly dcs: DefaultDcsService
readonly salts: FutureSaltsService
@ -51,6 +53,7 @@ export class StorageManager {
constructor(readonly options: StorageManagerOptions & StorageManagerExtraOptions) {
this.provider = this.options.provider
this.platform = this.options.platform
this.driver = this.provider.driver
this.log = this.options.log.create('storage')
@ -69,10 +72,10 @@ export class StorageManager {
private _cleanupRestore?: () => void
private _load = asyncResettable(async () => {
this.driver.setup?.(this.log)
this.driver.setup?.(this.log, this.platform)
if (this.options.cleanup ?? true) {
this._cleanupRestore = getPlatform().beforeExit(() => {
this._cleanupRestore = this.platform.beforeExit(() => {
this._destroy().catch(err => this.log.error('cleanup error: %e', err))
})
}

View file

@ -1,3 +1,4 @@
export * from './errors.js'
export * from './peers.js'
export * from './utils.js'
export * from './platform.js'

View file

@ -0,0 +1,17 @@
import type { UploadFileLike } from '../highlevel/types/files/utils.js'
import type { MaybePromise } from './index.js'
export interface ICorePlatform {
beforeExit: (fn: () => void) => () => void
log: (color: number, level: number, tag: string, fmt: string, args: unknown[]) => void
getDefaultLogLevel: () => number | null
getDeviceModel: () => string
normalizeFile?: (file: UploadFileLike) => MaybePromise<{
file?: UploadFileLike
fileSize?: number
fileName?: string
} | null>
onNetworkChanged?: (fn: (connected: boolean) => void) => () => void
isOnline?: () => boolean
}

View file

@ -1,12 +1,13 @@
import Long from 'long'
import { describe, expect, it, vi } from 'vitest'
import { tl } from '@mtcute/tl'
import { defaultPlatform } from '@mtcute/test'
import { LogManager } from './logger.js'
describe('logger', () => {
const createManager = () => {
const mgr = new LogManager()
const mgr = new LogManager(undefined, defaultPlatform)
mgr.level = LogManager.INFO
const spy = vi.fn<typeof mgr.handler>()

View file

@ -1,8 +1,7 @@
import { tl } from '@mtcute/tl'
import { hex } from '@fuman/utils'
import type { ICorePlatform } from '../platform.js'
import { getPlatform } from '../platform.js'
import type { ICorePlatform } from '../types/platform.js'
import { isTlRpcError } from './type-assertions.js'
@ -160,19 +159,17 @@ export class LogManager extends Logger {
static DEBUG = 4
static VERBOSE = 5
readonly platform: ICorePlatform
level: number
handler: (color: number, level: number, tag: string, fmt: string, args: unknown[]) => void
constructor(tag = 'base') {
constructor(tag = 'base', platform: ICorePlatform) {
// workaround because we cant pass this to super
// eslint-disable-next-line ts/no-unsafe-argument
super(null as any, tag)
;(this as any).mgr = this
this.platform = getPlatform()
this.level = this.platform.getDefaultLogLevel() ?? DEFAULT_LOG_LEVEL
this.handler = this.platform.log.bind(this.platform)
this.level = platform.getDefaultLogLevel() ?? DEFAULT_LOG_LEVEL
this.handler = platform.log.bind(platform)
}
private _filter: (tag: string) => boolean = defaultFilter

View file

@ -11,17 +11,17 @@ import {
BaseTelegramClient as BaseTelegramClientBase,
TelegramClient as TelegramClientBase,
} from '@mtcute/core/client.js'
import { setPlatform } from '@mtcute/core/platform.js'
import { downloadToFile } from './methods/download-file.js'
import { DenoPlatform } from './platform.js'
import { SqliteStorage } from './sqlite/index.js'
import { DenoCryptoProvider } from './utils/crypto.js'
import { TcpTransport } from './utils/tcp.js'
export type { TelegramClientOptions }
export interface BaseTelegramClientOptions
extends PartialOnly<Omit<BaseTelegramClientOptionsBase, 'storage'>, 'transport' | 'crypto'> {
extends PartialOnly<Omit<BaseTelegramClientOptionsBase, 'storage'>, 'transport' | 'crypto' | 'platform'> {
/**
* Storage to use for this client.
*
@ -31,23 +31,14 @@ export interface BaseTelegramClientOptions
* @default `"client.session"`
*/
storage?: string | ITelegramStorageProvider
/**
* **ADVANCED USE ONLY**
*
* Whether to not set up the platform.
* This is useful if you call `setPlatform` yourself.
*/
platformless?: boolean
}
export class BaseTelegramClient extends BaseTelegramClientBase {
constructor(opts: BaseTelegramClientOptions) {
if (!opts.platformless) setPlatform(new DenoPlatform())
super({
crypto: new DenoCryptoProvider(),
transport: {} as any, // todo
transport: TcpTransport,
platform: new DenoPlatform(),
...opts,
storage:
typeof opts.storage === 'string'

View file

@ -1,4 +1,4 @@
import type { ICorePlatform } from '@mtcute/core/platform.js'
import type { ICorePlatform } from '@mtcute/core'
import { defaultLoggingHandler } from './common-internals-web/logging.js'
import { beforeExit } from './utils/exit-hook.js'

View file

@ -1,6 +1,7 @@
import { afterAll, beforeAll, describe } from 'vitest'
import { LogManager } from '@mtcute/core/utils.js'
import {
defaultPlatform,
testAuthKeysRepository,
testKeyValueRepository,
testPeersRepository,
@ -17,7 +18,7 @@ if (import.meta.env.TEST_ENV === 'deno') {
const storage = new SqliteStorage(':memory:')
beforeAll(async () => {
storage.driver.setup(new LogManager())
storage.driver.setup(new LogManager(undefined, defaultPlatform), defaultPlatform)
await storage.driver.load()
})

View file

@ -1,11 +1,9 @@
import { setPlatform } from '@mtcute/core/platform.js'
import type {
ClientMessageHandler,
RespondFn,
SendFn,
SomeWorker,
TelegramWorkerOptions,
TelegramWorkerPortOptions,
WorkerCustomMethods,
WorkerMessageHandler,
} from '@mtcute/core/worker.js'
@ -16,8 +14,10 @@ import {
import { DenoPlatform } from './platform.js'
export type { TelegramWorkerOptions, TelegramWorkerPortOptions, WorkerCustomMethods }
export type { TelegramWorkerOptions, WorkerCustomMethods }
export interface TelegramWorkerPortOptions {
worker: SomeWorker
}
let _registered = false
export class TelegramWorker<T extends WorkerCustomMethods> extends TelegramWorkerBase<T> {
@ -44,9 +44,11 @@ export class TelegramWorker<T extends WorkerCustomMethods> extends TelegramWorke
const platform = new DenoPlatform()
export class TelegramWorkerPort<T extends WorkerCustomMethods> extends TelegramWorkerPortBase<T> {
constructor(readonly options: TelegramWorkerPortOptions) {
setPlatform(platform)
super(options)
constructor(options: TelegramWorkerPortOptions) {
super({
worker: options.worker,
platform,
})
}
connectToWorker(worker: SomeWorker, handler: ClientMessageHandler): [SendFn, () => void] {

View file

@ -11,15 +11,13 @@ import {
BaseTelegramClient as BaseTelegramClientBase,
TelegramClient as TelegramClientBase,
} from '@mtcute/core/client.js'
import { setPlatform } from '@mtcute/core/platform.js'
import { NodePlatform } from './common-internals-node/platform.js'
import { downloadToFile } from './methods/download-file.js'
import { downloadAsNodeStream } from './methods/download-node-stream.js'
import { SqliteStorage } from './sqlite/index.js'
import { NodeCryptoProvider } from './utils/crypto.js'
import { TcpTransport } from './utils/tcp.js'
// import { TcpTransport } from './utils/tcp.js'
import { NodePlatform } from './common-internals-node/platform.js'
export type { TelegramClientOptions }
@ -34,7 +32,7 @@ try {
} catch {}
export interface BaseTelegramClientOptions
extends PartialOnly<Omit<BaseTelegramClientOptionsBase, 'storage'>, 'transport' | 'crypto'> {
extends PartialOnly<Omit<BaseTelegramClientOptionsBase, 'storage'>, 'transport' | 'crypto' | 'platform'> {
/**
* Storage to use for this client.
*
@ -45,23 +43,15 @@ export interface BaseTelegramClientOptions
*/
storage?: string | ITelegramStorageProvider
/**
* **ADVANCED USE ONLY**
*
* Whether to not set up the platform.
* This is useful if you call `setPlatform` yourself.
*/
platformless?: boolean
}
export class BaseTelegramClient extends BaseTelegramClientBase {
constructor(opts: BaseTelegramClientOptions) {
if (!opts.platformless) setPlatform(new NodePlatform())
super({
// eslint-disable-next-line
crypto: nativeCrypto ? new nativeCrypto() : new NodeCryptoProvider(),
transport: TcpTransport,
platform: new NodePlatform(),
...opts,
storage:
typeof opts.storage === 'string'

View file

@ -1,14 +1,12 @@
import * as os from 'node:os'
import type { ICorePlatform } from '@mtcute/core/platform.js'
import type { ICorePlatform } from '@mtcute/core'
import { normalizeFile } from '../utils/normalize-file.js'
import { beforeExit } from './exit-hook.js'
import { defaultLoggingHandler } from './logging.js'
const toBuffer = (buf: Uint8Array): Buffer => Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength)
export class NodePlatform implements ICorePlatform {
// ICorePlatform
declare log: typeof defaultLoggingHandler

View file

@ -1,6 +1,7 @@
import { afterAll, beforeAll, describe } from 'vitest'
import { LogManager } from '@mtcute/core/utils.js'
import {
defaultPlatform,
testAuthKeysRepository,
testKeyValueRepository,
testPeersRepository,
@ -14,7 +15,7 @@ if (import.meta.env.TEST_ENV === 'node') {
const storage = new SqliteStorage(':memory:')
beforeAll(async () => {
storage.driver.setup(new LogManager())
storage.driver.setup(new LogManager(undefined, defaultPlatform), defaultPlatform)
await storage.driver.load()
})

View file

@ -1,13 +1,11 @@
import { Worker, parentPort } from 'node:worker_threads'
import { setPlatform } from '@mtcute/core/platform.js'
import type {
ClientMessageHandler,
RespondFn,
SendFn,
SomeWorker,
TelegramWorkerOptions,
TelegramWorkerPortOptions,
WorkerCustomMethods,
WorkerMessageHandler,
} from '@mtcute/core/worker.js'
@ -18,7 +16,11 @@ import {
import { NodePlatform } from './common-internals-node/platform.js'
export type { TelegramWorkerOptions, TelegramWorkerPortOptions, WorkerCustomMethods }
export type { TelegramWorkerOptions, WorkerCustomMethods }
export interface TelegramWorkerPortOptions {
worker: SomeWorker
}
let _registered = false
@ -45,9 +47,11 @@ export class TelegramWorker<T extends WorkerCustomMethods> extends TelegramWorke
}
export class TelegramWorkerPort<T extends WorkerCustomMethods> extends TelegramWorkerPortBase<T> {
constructor(readonly options: TelegramWorkerPortOptions) {
setPlatform(new NodePlatform())
super(options)
constructor(options: TelegramWorkerPortOptions) {
super({
worker: options.worker,
platform: new NodePlatform(),
})
}
connectToWorker(worker: SomeWorker, handler: ClientMessageHandler): [SendFn, () => void] {

View file

@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { getPlatform } from '@mtcute/core/platform.js'
import { hex } from '@fuman/utils'
import { StubTelegramClient } from './client.js'
import { createStub } from './stub.js'
@ -25,7 +25,7 @@ describe('client stub', () => {
const client = new StubTelegramClient()
client.onRawMessage((msg) => {
log.push(`message ctor=${getPlatform().hexEncode(msg.subarray(0, 4))}`)
log.push(`message ctor=${hex.encode(msg.subarray(0, 4))}`)
client.close().catch(() => {})
})

View file

@ -3,7 +3,7 @@ import { IntermediatePacketCodec, tl } from '@mtcute/core'
import type { BaseTelegramClientOptions } from '@mtcute/core/client.js'
import { BaseTelegramClient } from '@mtcute/core/client.js'
import { defaultCryptoProvider } from './platform.js'
import { defaultCryptoProvider, defaultPlatform } from './platform.js'
import { StubMemoryTelegramStorage } from './storage.js'
// import { StubTelegramTransport } from './transport.js'
import type { InputResponder } from './types.js'
@ -57,6 +57,7 @@ export class StubTelegramClient extends BaseTelegramClient {
packetCodec: () => new IntermediatePacketCodec(),
},
crypto: defaultCryptoProvider,
platform: defaultPlatform,
...params,
})
}

View file

@ -7,17 +7,16 @@ import {
BaseTelegramClient as BaseTelegramClientBase,
TelegramClient as TelegramClientBase,
} from '@mtcute/core/client.js'
import { setPlatform } from '@mtcute/core/platform.js'
import { WebCryptoProvider } from './crypto.js'
import { IdbStorage } from './idb/index.js'
import { WebPlatform } from './platform.js'
import { WebSocketTransport } from './websocket.js'
import { WebPlatform } from './platform.js'
export type { TelegramClientOptions }
export interface BaseTelegramClientOptions
extends PartialOnly<Omit<BaseTelegramClientOptionsBase, 'storage'>, 'transport' | 'crypto'> {
extends PartialOnly<Omit<BaseTelegramClientOptionsBase, 'storage'>, 'transport' | 'crypto' | 'platform'> {
/**
* Storage to use for this client.
*
@ -27,23 +26,14 @@ export interface BaseTelegramClientOptions
* @default `"client.session"`
*/
storage?: string | ITelegramStorageProvider
/**
* **ADVANCED USE ONLY**
*
* Whether to not set up the platform.
* This is useful if you call `setPlatform` yourself.
*/
platformless?: boolean
}
export class BaseTelegramClient extends BaseTelegramClientBase {
constructor(opts: BaseTelegramClientOptions) {
if (!opts.platformless) setPlatform(new WebPlatform())
super({
crypto: new WebCryptoProvider(),
transport: new WebSocketTransport(),
platform: new WebPlatform(),
...opts,
storage:
typeof opts.storage === 'string'

View file

@ -1,4 +1,4 @@
import type { ICorePlatform } from '@mtcute/core/platform.js'
import type { ICorePlatform } from '@mtcute/core'
import { defaultLoggingHandler } from './common-internals-web/logging.js'
import { beforeExit } from './exit-hook.js'

View file

@ -1,12 +1,10 @@
/* eslint-disable no-restricted-globals */
import { setPlatform } from '@mtcute/core/platform.js'
import type {
ClientMessageHandler,
RespondFn,
SendFn,
SomeWorker,
TelegramWorkerOptions,
TelegramWorkerPortOptions,
WorkerCustomMethods,
WorkerMessageHandler,
} from '@mtcute/core/worker.js'
@ -17,8 +15,10 @@ import {
import { WebPlatform } from './platform.js'
export type { TelegramWorkerOptions, TelegramWorkerPortOptions, WorkerCustomMethods }
export type { TelegramWorkerOptions, WorkerCustomMethods }
export interface TelegramWorkerPortOptions {
worker: SomeWorker
}
let _registered = false
export class TelegramWorker<T extends WorkerCustomMethods> extends TelegramWorkerBase<T> {
@ -103,12 +103,14 @@ export class TelegramWorker<T extends WorkerCustomMethods> extends TelegramWorke
}
}
const platform = new WebPlatform()
const platform = /* #__PURE__ */ new WebPlatform()
export class TelegramWorkerPort<T extends WorkerCustomMethods> extends TelegramWorkerPortBase<T> {
constructor(readonly options: TelegramWorkerPortOptions) {
setPlatform(platform)
super(options)
constructor(options: TelegramWorkerPortOptions) {
super({
worker: options.worker,
platform,
})
}
connectToWorker(worker: SomeWorker, handler: ClientMessageHandler): [SendFn, () => void] {

View file

@ -132,9 +132,9 @@ importers:
packages/convert:
dependencies:
'@fuman/ip':
'@fuman/net':
specifier: workspace:^
version: link:../../private/fuman/packages/ip
version: link:../../private/fuman/packages/net
'@fuman/utils':
specifier: workspace:^
version: link:../../private/fuman/packages/utils