chore: worker
This commit is contained in:
parent
791a3a58b5
commit
cbc2002781
19 changed files with 320 additions and 273 deletions
|
@ -16,6 +16,7 @@
|
||||||
".": "./src/index.ts",
|
".": "./src/index.ts",
|
||||||
"./utils.js": "./src/utils/index.ts",
|
"./utils.js": "./src/utils/index.ts",
|
||||||
"./client.js": "./src/highlevel/client.ts",
|
"./client.js": "./src/highlevel/client.ts",
|
||||||
|
"./worker.js": "./src/highlevel/worker/index.ts",
|
||||||
"./methods.js": "./src/highlevel/methods.ts",
|
"./methods.js": "./src/highlevel/methods.ts",
|
||||||
"./platform.js": "./src/platform.ts"
|
"./platform.js": "./src/platform.ts"
|
||||||
},
|
},
|
||||||
|
@ -40,6 +41,10 @@
|
||||||
"./client.js": {
|
"./client.js": {
|
||||||
"import": "./esm/highlevel/client.js",
|
"import": "./esm/highlevel/client.js",
|
||||||
"require": "./cjs/highlevel/client.js"
|
"require": "./cjs/highlevel/client.js"
|
||||||
|
},
|
||||||
|
"./worker.js": {
|
||||||
|
"import": "./esm/highlevel/worker/index.js",
|
||||||
|
"require": "./cjs/highlevel/worker/index.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -689,6 +689,7 @@ on(name: string, handler: (...args: any[]) => void): this\n`)
|
||||||
output.write('}\n')
|
output.write('}\n')
|
||||||
|
|
||||||
output.write('\nexport type { TelegramClientOptions }\n')
|
output.write('\nexport type { TelegramClientOptions }\n')
|
||||||
|
output.write('\nexport * from "./base.js"\n')
|
||||||
output.write('\nexport class TelegramClient extends EventEmitter implements ITelegramClient {\n')
|
output.write('\nexport class TelegramClient extends EventEmitter implements ITelegramClient {\n')
|
||||||
|
|
||||||
output.write(' _client: ITelegramClient\n')
|
output.write(' _client: ITelegramClient\n')
|
||||||
|
|
|
@ -5135,6 +5135,8 @@ export interface TelegramClient extends ITelegramClient {
|
||||||
|
|
||||||
export type { TelegramClientOptions }
|
export type { TelegramClientOptions }
|
||||||
|
|
||||||
|
export * from './base.js'
|
||||||
|
|
||||||
export class TelegramClient extends EventEmitter implements ITelegramClient {
|
export class TelegramClient extends EventEmitter implements ITelegramClient {
|
||||||
_client: ITelegramClient
|
_client: ITelegramClient
|
||||||
constructor(opts: TelegramClientOptions) {
|
constructor(opts: TelegramClientOptions) {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
export * from './base.js'
|
|
||||||
export * from './client.types.js'
|
export * from './client.types.js'
|
||||||
export * from './storage/index.js'
|
export * from './storage/index.js'
|
||||||
export * from './types/index.js'
|
export * from './types/index.js'
|
||||||
|
|
3
packages/core/src/highlevel/worker/index.ts
Normal file
3
packages/core/src/highlevel/worker/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './port.js'
|
||||||
|
export * from './protocol.js'
|
||||||
|
export * from './worker.js'
|
|
@ -1,20 +0,0 @@
|
||||||
import { Worker } from 'worker_threads'
|
|
||||||
|
|
||||||
import { ClientMessageHandler, SendFn, SomeWorker } from '../protocol.js'
|
|
||||||
|
|
||||||
export function connectToWorker(worker: SomeWorker, handler: ClientMessageHandler): [SendFn, () => void] {
|
|
||||||
if (!(worker instanceof Worker)) {
|
|
||||||
throw new Error('Only worker_threads are supported')
|
|
||||||
}
|
|
||||||
|
|
||||||
const send: SendFn = worker.postMessage.bind(worker)
|
|
||||||
|
|
||||||
worker.on('message', handler)
|
|
||||||
|
|
||||||
return [
|
|
||||||
send,
|
|
||||||
() => {
|
|
||||||
worker.off('message', handler)
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
import { getPlatform } from '../../../platform.js'
|
|
||||||
import { ClientMessageHandler, SendFn, SomeWorker } from '../protocol.js'
|
|
||||||
|
|
||||||
export function connectToWorker(worker: SomeWorker, handler: ClientMessageHandler): [SendFn, () => void] {
|
|
||||||
if (worker instanceof Worker) {
|
|
||||||
const send: SendFn = worker.postMessage.bind(worker)
|
|
||||||
|
|
||||||
const messageHandler = (ev: MessageEvent) => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
||||||
handler(ev.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
worker.addEventListener('message', messageHandler)
|
|
||||||
|
|
||||||
return [
|
|
||||||
send,
|
|
||||||
() => {
|
|
||||||
worker.removeEventListener('message', messageHandler)
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (worker instanceof SharedWorker) {
|
|
||||||
const send: SendFn = worker.port.postMessage.bind(worker.port)
|
|
||||||
|
|
||||||
const pingInterval = setInterval(() => {
|
|
||||||
worker.port.postMessage({ __type__: 'ping' })
|
|
||||||
}, 10000)
|
|
||||||
|
|
||||||
const messageHandler = (ev: MessageEvent) => {
|
|
||||||
if (ev.data.__type__ === 'timeout') {
|
|
||||||
// we got disconnected from the worker due to timeout
|
|
||||||
// if the page is still alive (which is unlikely), we should reconnect
|
|
||||||
// however it's not really possible with SharedWorker API without re-creating the worker
|
|
||||||
// so we just reload the page for now
|
|
||||||
location.reload()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
||||||
handler(ev.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
worker.port.addEventListener('message', messageHandler)
|
|
||||||
worker.port.start()
|
|
||||||
|
|
||||||
const close = () => {
|
|
||||||
clearInterval(pingInterval)
|
|
||||||
worker.port.postMessage({ __type__: 'close' })
|
|
||||||
worker.port.removeEventListener('message', messageHandler)
|
|
||||||
worker.port.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
getPlatform().beforeExit(close)
|
|
||||||
|
|
||||||
return [send, close]
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('Only workers and shared workers are supported')
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
import { parentPort } from 'worker_threads'
|
|
||||||
|
|
||||||
import { RespondFn, WorkerMessageHandler } from '../protocol.js'
|
|
||||||
|
|
||||||
const registered = false
|
|
||||||
|
|
||||||
export function registerWorker(handler: WorkerMessageHandler): RespondFn {
|
|
||||||
if (!parentPort) {
|
|
||||||
throw new Error('registerWorker() must be called from a worker thread')
|
|
||||||
}
|
|
||||||
if (registered) {
|
|
||||||
throw new Error('registerWorker() must be called only once')
|
|
||||||
}
|
|
||||||
|
|
||||||
const port = parentPort
|
|
||||||
|
|
||||||
const respond: RespondFn = port.postMessage.bind(port)
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
||||||
parentPort.on('message', (message) => handler(message, respond))
|
|
||||||
|
|
||||||
return respond
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
import { RespondFn, WorkerMessageHandler } from '../protocol.js'
|
|
||||||
|
|
||||||
const registered = false
|
|
||||||
|
|
||||||
export function registerWorker(handler: WorkerMessageHandler): RespondFn {
|
|
||||||
if (registered) {
|
|
||||||
throw new Error('registerWorker() must be called only once')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) {
|
|
||||||
const respond: RespondFn = self.postMessage.bind(self)
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
||||||
self.addEventListener('message', (message) => handler(message.data, respond))
|
|
||||||
|
|
||||||
return respond
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof SharedWorkerGlobalScope !== 'undefined' && self instanceof SharedWorkerGlobalScope) {
|
|
||||||
const connections: MessagePort[] = []
|
|
||||||
|
|
||||||
const broadcast = (message: unknown) => {
|
|
||||||
for (const port of connections) {
|
|
||||||
port.postMessage(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.onconnect = (event) => {
|
|
||||||
const port = event.ports[0]
|
|
||||||
connections.push(port)
|
|
||||||
|
|
||||||
const respond = port.postMessage.bind(port)
|
|
||||||
|
|
||||||
// not very reliable, but better than nothing
|
|
||||||
// SharedWorker API doesn't provide a way to detect when the client closes the connection
|
|
||||||
// so we just assume that the client is done when it sends a 'close' message
|
|
||||||
// and keep a timeout for the case when the client closes without sending a 'close' message
|
|
||||||
const onClose = () => {
|
|
||||||
port.close()
|
|
||||||
const idx = connections.indexOf(port)
|
|
||||||
|
|
||||||
if (idx >= 0) {
|
|
||||||
connections.splice(connections.indexOf(port), 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onTimeout = () => {
|
|
||||||
console.warn('some connection timed out!')
|
|
||||||
respond({ __type__: 'timeout' })
|
|
||||||
onClose()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 60s should be a reasonable timeout considering that the client should send a ping every 10s
|
|
||||||
// so even if the browser has suspended the timers, we should still get a ping within a minute
|
|
||||||
let timeout = setTimeout(onTimeout, 60000)
|
|
||||||
|
|
||||||
port.addEventListener('message', (message) => {
|
|
||||||
if (message.data.__type__ === 'close') {
|
|
||||||
onClose()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.data.__type__ === 'ping') {
|
|
||||||
clearTimeout(timeout)
|
|
||||||
timeout = setTimeout(onTimeout, 60000)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
||||||
handler(message.data, respond)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return broadcast
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('registerWorker() must be called from a worker')
|
|
||||||
}
|
|
|
@ -6,17 +6,18 @@ 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'
|
||||||
import { WorkerInvoker } from './invoker.js'
|
import { WorkerInvoker } from './invoker.js'
|
||||||
import { connectToWorker } from './platform/connect.js'
|
import { ClientMessageHandler, SendFn, SomeWorker, WorkerCustomMethods } from './protocol.js'
|
||||||
import { ClientMessageHandler, SomeWorker, WorkerCustomMethods } from './protocol.js'
|
|
||||||
import { TelegramStorageProxy } from './storage.js'
|
import { TelegramStorageProxy } from './storage.js'
|
||||||
|
|
||||||
export interface TelegramWorkerPortOptions {
|
export interface TelegramWorkerPortOptions {
|
||||||
worker: SomeWorker
|
worker: SomeWorker
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TelegramWorkerPort<Custom extends WorkerCustomMethods> implements ITelegramClient {
|
export abstract class TelegramWorkerPort<Custom extends WorkerCustomMethods> implements ITelegramClient {
|
||||||
constructor(readonly options: TelegramWorkerPortOptions) {}
|
constructor(readonly options: TelegramWorkerPortOptions) {}
|
||||||
|
|
||||||
|
abstract connectToWorker(worker: SomeWorker, handler: ClientMessageHandler): [SendFn, () => void]
|
||||||
|
|
||||||
readonly log = new LogManager('worker')
|
readonly log = new LogManager('worker')
|
||||||
|
|
||||||
private _serverUpdatesHandler: (updates: tl.TypeUpdates) => void = () => {}
|
private _serverUpdatesHandler: (updates: tl.TypeUpdates) => void = () => {}
|
||||||
|
@ -60,7 +61,7 @@ export class TelegramWorkerPort<Custom extends WorkerCustomMethods> implements I
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _connection = connectToWorker(this.options.worker, this._onMessage)
|
private _connection = this.connectToWorker(this.options.worker, this._onMessage)
|
||||||
private _invoker = new WorkerInvoker(this._connection[0])
|
private _invoker = new WorkerInvoker(this._connection[0])
|
||||||
private _bind = this._invoker.makeBinder<ITelegramClient>('client')
|
private _bind = this._invoker.makeBinder<ITelegramClient>('client')
|
||||||
|
|
||||||
|
|
|
@ -1,40 +1,88 @@
|
||||||
import { BaseTelegramClient, BaseTelegramClientOptions } from '../base.js'
|
import { BaseTelegramClient } from '../base.js'
|
||||||
import { serializeError } from './errors.js'
|
import { serializeError } from './errors.js'
|
||||||
import { registerWorker } from './platform/register.js'
|
|
||||||
import { RespondFn, WorkerCustomMethods, WorkerInboundMessage, WorkerMessageHandler } from './protocol.js'
|
import { RespondFn, WorkerCustomMethods, WorkerInboundMessage, WorkerMessageHandler } from './protocol.js'
|
||||||
|
|
||||||
export interface TelegramWorkerOptions<T extends WorkerCustomMethods> {
|
export interface TelegramWorkerOptions<T extends WorkerCustomMethods> {
|
||||||
client: BaseTelegramClient | BaseTelegramClientOptions
|
client: BaseTelegramClient
|
||||||
customMethods?: T
|
customMethods?: T
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeTelegramWorker<T extends WorkerCustomMethods>(params: TelegramWorkerOptions<T>) {
|
export abstract class TelegramWorker<T extends WorkerCustomMethods> {
|
||||||
const { client: client_, customMethods } = params
|
readonly client: BaseTelegramClient
|
||||||
|
readonly broadcast: RespondFn
|
||||||
|
|
||||||
const client = client_ instanceof BaseTelegramClient ? client_ : new BaseTelegramClient(client_)
|
abstract registerWorker(handler: WorkerMessageHandler): RespondFn
|
||||||
|
|
||||||
const onInvoke = (msg: Extract<WorkerInboundMessage, { type: 'invoke' }>, respond: RespondFn) => {
|
constructor(readonly params: TelegramWorkerOptions<T>) {
|
||||||
|
this.broadcast = this.registerWorker((message, respond) => {
|
||||||
|
switch (message.type) {
|
||||||
|
case 'invoke':
|
||||||
|
this.onInvoke(message, respond)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const client = params.client
|
||||||
|
this.client = client
|
||||||
|
|
||||||
|
client.log.mgr.handler = (color, level, tag, fmt, args) =>
|
||||||
|
this.broadcast({
|
||||||
|
type: 'log',
|
||||||
|
color,
|
||||||
|
level,
|
||||||
|
tag,
|
||||||
|
fmt,
|
||||||
|
args,
|
||||||
|
})
|
||||||
|
client.onError((err) =>
|
||||||
|
this.broadcast({
|
||||||
|
type: 'error',
|
||||||
|
error: err,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (client.updates) {
|
||||||
|
client.onUpdate((update, peers) =>
|
||||||
|
this.broadcast({
|
||||||
|
type: 'update',
|
||||||
|
update,
|
||||||
|
users: peers.users,
|
||||||
|
chats: peers.chats,
|
||||||
|
hasMin: peers.hasMin,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
client.onServerUpdate((update) =>
|
||||||
|
this.broadcast({
|
||||||
|
type: 'server_update',
|
||||||
|
update,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onInvoke(msg: Extract<WorkerInboundMessage, { type: 'invoke' }>, respond: RespondFn) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
let target: any
|
let target: any
|
||||||
|
|
||||||
switch (msg.target) {
|
switch (msg.target) {
|
||||||
case 'custom':
|
case 'custom':
|
||||||
target = customMethods
|
target = this.params.customMethods
|
||||||
break
|
break
|
||||||
case 'client':
|
case 'client':
|
||||||
target = client
|
target = this.client
|
||||||
break
|
break
|
||||||
case 'storage':
|
case 'storage':
|
||||||
target = client.storage
|
target = this.client.storage
|
||||||
break
|
break
|
||||||
case 'storage-self':
|
case 'storage-self':
|
||||||
target = client.storage.self
|
target = this.client.storage.self
|
||||||
break
|
break
|
||||||
case 'storage-peers':
|
case 'storage-peers':
|
||||||
target = client.storage.peers
|
target = this.client.storage.peers
|
||||||
break
|
break
|
||||||
case 'app-config':
|
case 'app-config':
|
||||||
target = client.appConfig
|
target = this.client.appConfig
|
||||||
break
|
break
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
|
@ -80,49 +128,4 @@ export function makeTelegramWorker<T extends WorkerCustomMethods>(params: Telegr
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMessage: WorkerMessageHandler = (message, respond) => {
|
|
||||||
switch (message.type) {
|
|
||||||
case 'invoke':
|
|
||||||
onInvoke(message, respond)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const broadcast = registerWorker(onMessage)
|
|
||||||
|
|
||||||
client.log.mgr.handler = (color, level, tag, fmt, args) =>
|
|
||||||
broadcast({
|
|
||||||
type: 'log',
|
|
||||||
color,
|
|
||||||
level,
|
|
||||||
tag,
|
|
||||||
fmt,
|
|
||||||
args,
|
|
||||||
})
|
|
||||||
client.onError((err) =>
|
|
||||||
broadcast({
|
|
||||||
type: 'error',
|
|
||||||
error: err,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (client.updates) {
|
|
||||||
client.onUpdate((update, peers) =>
|
|
||||||
broadcast({
|
|
||||||
type: 'update',
|
|
||||||
update,
|
|
||||||
users: peers.users,
|
|
||||||
chats: peers.chats,
|
|
||||||
hasMin: peers.hasMin,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
client.onServerUpdate((update) =>
|
|
||||||
broadcast({
|
|
||||||
type: 'server_update',
|
|
||||||
update,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
import { createRequire } from 'module'
|
import { createRequire } from 'module'
|
||||||
import { createInterface, Interface as RlInterface } from 'readline'
|
import { createInterface, Interface as RlInterface } from 'readline'
|
||||||
|
|
||||||
import { FileDownloadLocation, FileDownloadParameters, User } from '@mtcute/core'
|
import { FileDownloadLocation, FileDownloadParameters, ITelegramStorageProvider, PartialOnly, User } from '@mtcute/core'
|
||||||
import { TelegramClient as TelegramClientBase, TelegramClientOptions } from '@mtcute/core/client.js'
|
import {
|
||||||
|
BaseTelegramClient as BaseTelegramClientBase,
|
||||||
|
BaseTelegramClientOptions as BaseTelegramClientOptionsBase,
|
||||||
|
TelegramClient as TelegramClientBase,
|
||||||
|
TelegramClientOptions,
|
||||||
|
} from '@mtcute/core/client.js'
|
||||||
import { setPlatform } from '@mtcute/core/platform.js'
|
import { setPlatform } from '@mtcute/core/platform.js'
|
||||||
import { SqliteStorage } from '@mtcute/sqlite'
|
import { SqliteStorage } from '@mtcute/sqlite'
|
||||||
|
|
||||||
|
@ -25,19 +30,23 @@ try {
|
||||||
nativeCrypto = require('@mtcute/crypto-node').NodeNativeCryptoProvider
|
nativeCrypto = require('@mtcute/crypto-node').NodeNativeCryptoProvider
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
/**
|
export interface BaseTelegramClientOptions
|
||||||
* Telegram client for use in Node.js
|
extends PartialOnly<Omit<BaseTelegramClientOptionsBase, 'storage'>, 'transport' | 'crypto'> {
|
||||||
|
/**
|
||||||
|
* Storage to use for this client.
|
||||||
|
*
|
||||||
|
* If a string is passed, it will be used as
|
||||||
|
* a name for an SQLite database file.
|
||||||
|
*
|
||||||
|
* @default `"client.session"`
|
||||||
*/
|
*/
|
||||||
export class TelegramClient extends TelegramClientBase {
|
storage?: string | ITelegramStorageProvider
|
||||||
constructor(opts: TelegramClientOptions) {
|
}
|
||||||
|
|
||||||
|
export class BaseTelegramClient extends BaseTelegramClientBase {
|
||||||
|
constructor(opts: BaseTelegramClientOptions) {
|
||||||
setPlatform(new NodePlatform())
|
setPlatform(new NodePlatform())
|
||||||
|
|
||||||
if ('client' in opts) {
|
|
||||||
super(opts)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
super({
|
super({
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
crypto: nativeCrypto ? new nativeCrypto() : new NodeCryptoProvider(),
|
crypto: nativeCrypto ? new nativeCrypto() : new NodeCryptoProvider(),
|
||||||
|
@ -49,6 +58,23 @@ export class TelegramClient extends TelegramClientBase {
|
||||||
opts.storage ?? new SqliteStorage('client.session'),
|
opts.storage ?? new SqliteStorage('client.session'),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Telegram client for use in Node.js
|
||||||
|
*/
|
||||||
|
export class TelegramClient extends TelegramClientBase {
|
||||||
|
constructor(opts: TelegramClientOptions) {
|
||||||
|
if ('client' in opts) {
|
||||||
|
super(opts)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
super({
|
||||||
|
client: new BaseTelegramClient(opts),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private _rl?: RlInterface
|
private _rl?: RlInterface
|
||||||
|
|
||||||
|
@ -120,10 +146,7 @@ export class TelegramClient extends TelegramClientBase {
|
||||||
return downloadToFile(this, filename, location, params)
|
return downloadToFile(this, filename, location, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadAsNodeStream(
|
downloadAsNodeStream(location: FileDownloadLocation, params?: FileDownloadParameters | undefined) {
|
||||||
location: FileDownloadLocation,
|
|
||||||
params?: FileDownloadParameters | undefined,
|
|
||||||
) {
|
|
||||||
return downloadAsNodeStream(this, location, params)
|
return downloadAsNodeStream(this, location, params)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ export * from './client.js'
|
||||||
export * from './platform.js'
|
export * from './platform.js'
|
||||||
export * from './utils/crypto.js'
|
export * from './utils/crypto.js'
|
||||||
export * from './utils/tcp.js'
|
export * from './utils/tcp.js'
|
||||||
|
export * from './worker.js'
|
||||||
export * from '@mtcute/core'
|
export * from '@mtcute/core'
|
||||||
export * from '@mtcute/html-parser'
|
export * from '@mtcute/html-parser'
|
||||||
export * from '@mtcute/markdown-parser'
|
export * from '@mtcute/markdown-parser'
|
||||||
|
|
67
packages/node/src/worker.ts
Normal file
67
packages/node/src/worker.ts
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import { parentPort, Worker } from 'worker_threads'
|
||||||
|
|
||||||
|
import { setPlatform } from '@mtcute/core/platform.js'
|
||||||
|
import {
|
||||||
|
ClientMessageHandler,
|
||||||
|
RespondFn,
|
||||||
|
SendFn,
|
||||||
|
SomeWorker,
|
||||||
|
TelegramWorker as TelegramWorkerBase,
|
||||||
|
TelegramWorkerOptions,
|
||||||
|
TelegramWorkerPort as TelegramWorkerPortBase,
|
||||||
|
TelegramWorkerPortOptions,
|
||||||
|
WorkerCustomMethods,
|
||||||
|
WorkerMessageHandler,
|
||||||
|
} from '@mtcute/core/worker.js'
|
||||||
|
|
||||||
|
import { NodePlatform } from './platform.js'
|
||||||
|
|
||||||
|
export type { TelegramWorkerOptions, TelegramWorkerPortOptions, WorkerCustomMethods }
|
||||||
|
|
||||||
|
let _registered = false
|
||||||
|
|
||||||
|
export class TelegramWorker<T extends WorkerCustomMethods> extends TelegramWorkerBase<T> {
|
||||||
|
registerWorker(handler: WorkerMessageHandler): RespondFn {
|
||||||
|
if (!parentPort) {
|
||||||
|
throw new Error('TelegramWorker must be created from a worker thread')
|
||||||
|
}
|
||||||
|
if (_registered) {
|
||||||
|
throw new Error('TelegramWorker must be created only once')
|
||||||
|
}
|
||||||
|
|
||||||
|
_registered = true
|
||||||
|
|
||||||
|
const port = parentPort
|
||||||
|
|
||||||
|
const respond: RespondFn = port.postMessage.bind(port)
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||||
|
parentPort.on('message', (message) => handler(message, respond))
|
||||||
|
|
||||||
|
return respond
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TelegramWorkerPort<T extends WorkerCustomMethods> extends TelegramWorkerPortBase<T> {
|
||||||
|
constructor(readonly options: TelegramWorkerPortOptions) {
|
||||||
|
setPlatform(new NodePlatform())
|
||||||
|
super(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
connectToWorker(worker: SomeWorker, handler: ClientMessageHandler): [SendFn, () => void] {
|
||||||
|
if (!(worker instanceof Worker)) {
|
||||||
|
throw new Error('Only worker_threads are supported')
|
||||||
|
}
|
||||||
|
|
||||||
|
const send: SendFn = worker.postMessage.bind(worker)
|
||||||
|
|
||||||
|
worker.on('message', handler)
|
||||||
|
|
||||||
|
return [
|
||||||
|
send,
|
||||||
|
() => {
|
||||||
|
worker.off('message', handler)
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,5 @@
|
||||||
import {
|
import { MaybePromise, MustEqual, RpcCallOptions, tl } from '@mtcute/core'
|
||||||
BaseTelegramClient,
|
import { BaseTelegramClient, BaseTelegramClientOptions } from '@mtcute/core/client.js'
|
||||||
BaseTelegramClientOptions,
|
|
||||||
MaybePromise,
|
|
||||||
MustEqual,
|
|
||||||
RpcCallOptions,
|
|
||||||
tl,
|
|
||||||
} from '@mtcute/core'
|
|
||||||
|
|
||||||
import { defaultCryptoProvider } from './platform.js'
|
import { defaultCryptoProvider } from './platform.js'
|
||||||
import { StubMemoryTelegramStorage } from './storage.js'
|
import { StubMemoryTelegramStorage } from './storage.js'
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import { BaseTelegramClient, MemoryStorage } from '@mtcute/core'
|
import { MemoryStorage } from '@mtcute/core'
|
||||||
|
import { BaseTelegramClient } from '@mtcute/core/client.js'
|
||||||
|
|
||||||
import { defaultCryptoProvider } from './platform.js'
|
import { defaultCryptoProvider } from './platform.js'
|
||||||
import { createStub } from './stub.js'
|
import { createStub } from './stub.js'
|
||||||
|
|
61
packages/web/src/client.ts
Normal file
61
packages/web/src/client.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import { ITelegramStorageProvider, PartialOnly } from '@mtcute/core'
|
||||||
|
import {
|
||||||
|
BaseTelegramClient as BaseTelegramClientBase,
|
||||||
|
BaseTelegramClientOptions as BaseTelegramClientOptionsBase,
|
||||||
|
TelegramClient as TelegramClientBase,
|
||||||
|
TelegramClientOptions,
|
||||||
|
} 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'
|
||||||
|
|
||||||
|
export type { TelegramClientOptions }
|
||||||
|
|
||||||
|
export interface BaseTelegramClientOptions
|
||||||
|
extends PartialOnly<Omit<BaseTelegramClientOptionsBase, 'storage'>, 'transport' | 'crypto'> {
|
||||||
|
/**
|
||||||
|
* Storage to use for this client.
|
||||||
|
*
|
||||||
|
* If a string is passed, it will be used as
|
||||||
|
* a name for an IndexedDB database.
|
||||||
|
*
|
||||||
|
* @default `"client.session"`
|
||||||
|
*/
|
||||||
|
storage?: string | ITelegramStorageProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BaseTelegramClient extends BaseTelegramClientBase {
|
||||||
|
constructor(opts: BaseTelegramClientOptions) {
|
||||||
|
setPlatform(new WebPlatform())
|
||||||
|
|
||||||
|
super({
|
||||||
|
crypto: new WebCryptoProvider(),
|
||||||
|
transport: () => new WebSocketTransport(),
|
||||||
|
...opts,
|
||||||
|
storage:
|
||||||
|
typeof opts.storage === 'string' ?
|
||||||
|
new IdbStorage(opts.storage) :
|
||||||
|
opts.storage ?? new IdbStorage('client.session'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Telegram client for use in Node.js
|
||||||
|
*/
|
||||||
|
export class TelegramClient extends TelegramClientBase {
|
||||||
|
constructor(opts: TelegramClientOptions) {
|
||||||
|
if ('client' in opts) {
|
||||||
|
super(opts)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
super({
|
||||||
|
client: new BaseTelegramClient(opts),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,6 @@
|
||||||
|
export * from './client.js'
|
||||||
export * from './crypto.js'
|
export * from './crypto.js'
|
||||||
export * from './idb/index.js'
|
export * from './idb/index.js'
|
||||||
export * from './platform.js'
|
export * from './platform.js'
|
||||||
|
export * from './worker.js'
|
||||||
|
export * from '@mtcute/core'
|
||||||
|
|
67
packages/web/src/worker.ts
Normal file
67
packages/web/src/worker.ts
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import { parentPort, Worker } from 'worker_threads'
|
||||||
|
|
||||||
|
import { setPlatform } from '@mtcute/core/platform.js'
|
||||||
|
import {
|
||||||
|
ClientMessageHandler,
|
||||||
|
RespondFn,
|
||||||
|
SendFn,
|
||||||
|
SomeWorker,
|
||||||
|
TelegramWorker as TelegramWorkerBase,
|
||||||
|
TelegramWorkerOptions,
|
||||||
|
TelegramWorkerPort as TelegramWorkerPortBase,
|
||||||
|
TelegramWorkerPortOptions,
|
||||||
|
WorkerCustomMethods,
|
||||||
|
WorkerMessageHandler,
|
||||||
|
} from '@mtcute/core/worker.js'
|
||||||
|
|
||||||
|
import { WebPlatform } from './platform.js'
|
||||||
|
|
||||||
|
export type { TelegramWorkerOptions, TelegramWorkerPortOptions, WorkerCustomMethods }
|
||||||
|
|
||||||
|
let _registered = false
|
||||||
|
|
||||||
|
export class TelegramWorker<T extends WorkerCustomMethods> extends TelegramWorkerBase<T> {
|
||||||
|
registerWorker(handler: WorkerMessageHandler): RespondFn {
|
||||||
|
if (!parentPort) {
|
||||||
|
throw new Error('TelegramWorker must be created from a worker thread')
|
||||||
|
}
|
||||||
|
if (_registered) {
|
||||||
|
throw new Error('TelegramWorker must be created only once')
|
||||||
|
}
|
||||||
|
|
||||||
|
_registered = true
|
||||||
|
|
||||||
|
const port = parentPort
|
||||||
|
|
||||||
|
const respond: RespondFn = port.postMessage.bind(port)
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||||
|
parentPort.on('message', (message) => handler(message, respond))
|
||||||
|
|
||||||
|
return respond
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TelegramWorkerPort<T extends WorkerCustomMethods> extends TelegramWorkerPortBase<T> {
|
||||||
|
constructor(readonly options: TelegramWorkerPortOptions) {
|
||||||
|
setPlatform(new WebPlatform())
|
||||||
|
super(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
connectToWorker(worker: SomeWorker, handler: ClientMessageHandler): [SendFn, () => void] {
|
||||||
|
if (!(worker instanceof Worker)) {
|
||||||
|
throw new Error('Only worker_threads are supported')
|
||||||
|
}
|
||||||
|
|
||||||
|
const send: SendFn = worker.postMessage.bind(worker)
|
||||||
|
|
||||||
|
worker.on('message', handler)
|
||||||
|
|
||||||
|
return [
|
||||||
|
send,
|
||||||
|
() => {
|
||||||
|
worker.off('message', handler)
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue