fix: more fixes for web

This commit is contained in:
alina 🌸 2023-10-21 01:42:27 +03:00
parent c1f679c74c
commit 15c855df84
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
7 changed files with 92 additions and 71 deletions

View file

@ -30,6 +30,10 @@
} }
} }
}, },
"browser": {
"./cjs/methods/files/_platform.js": "./cjs/methods/files/_platform.web.js",
"./esm/methods/files/_platform.js": "./esm/methods/files/_platform.web.js"
},
"dependencies": { "dependencies": {
"@types/node": "18.16.0", "@types/node": "18.16.0",
"@mtcute/core": "workspace:^1.0.0", "@mtcute/core": "workspace:^1.0.0",

View file

@ -0,0 +1,32 @@
import { createReadStream, promises, ReadStream } from 'node:fs'
import { basename } from 'node:path'
import { Readable } from 'node:stream'
import { nodeReadableToWeb } from '../../utils/stream-utils.js'
/** @internal */
export function _createFileStream(path: string): ReadStream {
return createReadStream(path)
}
/** @internal */
export function _isFileStream(stream: unknown): stream is ReadStream {
return stream instanceof ReadStream
}
/** @internal */
export async function _extractFileStreamMeta(stream: ReadStream): Promise<[string, number]> {
const fileName = basename(stream.path.toString())
const fileSize = await promises.stat(stream.path.toString()).then((stat) => stat.size)
return [fileName, fileSize]
}
/** @internal */
export function _handleNodeStream<T>(val: T | Readable): T | ReadableStream<Uint8Array> {
if (val instanceof Readable) {
return nodeReadableToWeb(val)
}
return val
}

View file

@ -0,0 +1,23 @@
import { MtArgumentError } from '@mtcute/core'
/** @internal */
export function _createFileStream(): never {
throw new MtArgumentError('Cannot create file stream on web platform')
}
/** @internal */
export function _isFileStream() {
return false
}
/** @internal */
export function _extractFileStreamMeta(): never {
throw new Error('UNREACHABLE')
}
/** @internal */
export function _handleNodeStream(val: unknown) {
return val
}
// all the above functions shall be inlined by terser

View file

@ -1,30 +1,15 @@
import fileType from 'file-type' import fileType from 'file-type'
// eslint-disable-next-line no-restricted-imports
import type { ReadStream } from 'fs'
import { createRequire } from 'module'
import { BaseTelegramClient, MtArgumentError, tl } from '@mtcute/core' import { BaseTelegramClient, MtArgumentError, tl } from '@mtcute/core'
import { randomLong } from '@mtcute/core/utils.js' import { randomLong } from '@mtcute/core/utils.js'
import { UploadedFile, UploadFileLike } from '../../types/index.js' import { UploadedFile, UploadFileLike } from '../../types/index.js'
import { determinePartSize, isProbablyPlainText } from '../../utils/file-utils.js' import { determinePartSize, isProbablyPlainText } from '../../utils/file-utils.js'
import { bufferToStream, createChunkedReader, nodeReadableToWeb, streamToBuffer } from '../../utils/stream-utils.js' import { bufferToStream, createChunkedReader, streamToBuffer } from '../../utils/stream-utils.js'
import { _createFileStream, _extractFileStreamMeta, _handleNodeStream, _isFileStream } from './_platform.js'
const { fromBuffer: fileTypeFromBuffer } = fileType const { fromBuffer: fileTypeFromBuffer } = fileType
let fs: typeof import('fs') | null = null
let path: typeof import('path') | null = null
let nodeStream: typeof import('stream') | null = null
try {
// @only-if-esm
const require = createRequire(import.meta.url)
// @/only-if-esm
fs = require('fs') as typeof import('fs')
path = require('path') as typeof import('path')
nodeStream = require('stream') as typeof import('stream')
} catch (e) {}
const OVERRIDE_MIME: Record<string, string> = { const OVERRIDE_MIME: Record<string, string> = {
// tg doesn't interpret `audio/opus` files as voice messages for some reason // tg doesn't interpret `audio/opus` files as voice messages for some reason
'audio/opus': 'audio/ogg', 'audio/opus': 'audio/ogg',
@ -134,20 +119,11 @@ export async function uploadFile(
} }
if (typeof file === 'string') { if (typeof file === 'string') {
if (!fs) { file = _createFileStream(file)
throw new MtArgumentError('Local paths are only supported for NodeJS!')
}
file = fs.createReadStream(file)
} }
if (fs && file instanceof fs.ReadStream) { if (_isFileStream(file)) {
fileName = path!.basename(file.path.toString()) [fileName, fileSize] = await _extractFileStreamMeta(file)
fileSize = await new Promise((res, rej) => {
fs!.stat((file as ReadStream).path.toString(), (err, stat) => {
if (err) rej(err)
res(stat.size)
})
})
// fs.ReadStream is a subclass of Readable, will be handled below // fs.ReadStream is a subclass of Readable, will be handled below
} }
@ -186,9 +162,7 @@ export async function uploadFile(
file = file.body file = file.body
} }
if (nodeStream && file instanceof nodeStream.Readable) { file = _handleNodeStream(file)
file = nodeReadableToWeb(file)
}
if (!(file instanceof ReadableStream)) { if (!(file instanceof ReadableStream)) {
throw new MtArgumentError('Could not convert input `file` to stream!') throw new MtArgumentError('Could not convert input `file` to stream!')

View file

@ -1,18 +1,9 @@
/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-argument */
import { createRequire } from 'module'
import { base64Encode } from '@mtcute/core/utils.js' import { base64Encode } from '@mtcute/core/utils.js'
let util: typeof import('util') | null = null const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom')
try {
// @only-if-esm
const require = createRequire(import.meta.url)
// @/only-if-esm
util = require('util') as typeof import('util')
} catch (e) {}
// get all property names. unlike Object.getOwnPropertyNames, // get all property names. unlike Object.getOwnPropertyNames,
// also gets inherited property names // also gets inherited property names
@ -83,7 +74,5 @@ export function makeInspectable<T>(obj: new (...args: any[]) => T, props?: (keyo
// eslint-disable-next-line @typescript-eslint/no-unsafe-return // eslint-disable-next-line @typescript-eslint/no-unsafe-return
return ret return ret
} }
if (util) { obj.prototype[customInspectSymbol] = obj.prototype.toJSON
obj.prototype[util.inspect.custom] = obj.prototype.toJSON
}
} }

View file

@ -4,8 +4,6 @@ export * from './abstract.js'
export * from './intermediate.js' export * from './intermediate.js'
export * from './obfuscated.js' export * from './obfuscated.js'
export * from './streamed.js' export * from './streamed.js'
export * from './tcp.js'
export * from './websocket.js'
export * from './wrapped.js' export * from './wrapped.js'
import { _defaultTransportFactory } from '../../utils/platform/transport.js' import { _defaultTransportFactory } from '../../utils/platform/transport.js'

View file

@ -1,5 +1,4 @@
import EventEmitter from 'events' import EventEmitter from 'events'
import { createRequire } from 'module'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
@ -9,22 +8,8 @@ import { IPacketCodec, ITelegramTransport, TransportState } from './abstract.js'
import { IntermediatePacketCodec } from './intermediate.js' import { IntermediatePacketCodec } from './intermediate.js'
import { ObfuscatedPacketCodec } from './obfuscated.js' import { ObfuscatedPacketCodec } from './obfuscated.js'
let ws: { export type WebSocketConstructor = {
new (address: string, options?: string): WebSocket new (address: string, protocol?: string): WebSocket
} | null
if (typeof window === 'undefined' || typeof window.WebSocket === 'undefined') {
try {
// @only-if-esm
const require = createRequire(import.meta.url)
// @/only-if-esm
// eslint-disable-next-line
ws = require('ws')
} catch (e) {
ws = null
}
} else {
ws = window.WebSocket
} }
const subdomainsMap: Record<string, string> = { const subdomainsMap: Record<string, string> = {
@ -51,20 +36,36 @@ export abstract class BaseWebSocketTransport extends EventEmitter implements ITe
private _baseDomain: string private _baseDomain: string
private _subdomains: Record<string, string> private _subdomains: Record<string, string>
private _WebSocket: WebSocketConstructor
/** constructor({
* @param baseDomain Base WebSocket domain ws = WebSocket,
* @param subdomains Map of sub-domains (key is DC ID, value is string) baseDomain = 'web.telegram.org',
*/ subdomains = subdomainsMap,
constructor(baseDomain = 'web.telegram.org', subdomains = subdomainsMap) { }: {
/** Custom implementation of WebSocket (e.g. https://npm.im/ws) */
ws?: WebSocketConstructor
/** Base WebSocket domain */
baseDomain?: string
/** Map of sub-domains (key is DC ID, value is string) */
subdomains?: Record<string, string>
} = {}) {
super() super()
if (!ws) { if (!ws) {
throw new MtUnsupportedError('To use WebSocket transport with NodeJS, install `ws` package.') throw new MtUnsupportedError(
'To use WebSocket transport with NodeJS, install `ws` package and pass it to constructor',
)
}
// gotta love cjs/esm compat
if ('default' in ws) {
ws = ws.default as WebSocketConstructor
} }
this._baseDomain = baseDomain this._baseDomain = baseDomain
this._subdomains = subdomains this._subdomains = subdomains
this._WebSocket = ws
this.close = this.close.bind(this) this.close = this.close.bind(this)
} }
@ -104,7 +105,7 @@ export abstract class BaseWebSocketTransport extends EventEmitter implements ITe
this._state = TransportState.Connecting this._state = TransportState.Connecting
this._currentDc = dc this._currentDc = dc
this._socket = new ws!( this._socket = new this._WebSocket(
`wss://${this._subdomains[dc.id]}.${this._baseDomain}/apiws${testMode ? '_test' : ''}`, `wss://${this._subdomains[dc.id]}.${this._baseDomain}/apiws${testMode ? '_test' : ''}`,
'binary', 'binary',
) )