chore: moved build to @fuman/build
This commit is contained in:
parent
0692de2179
commit
9e3e379c25
103 changed files with 1926 additions and 3034 deletions
|
@ -6,7 +6,6 @@ import { globSync } from 'glob'
|
|||
export function collectTestEntrypoints(params: { skipPackages: string[], skipTests: string[] }) {
|
||||
const files: string[] = []
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const packages = resolve(__dirname, '../../packages')
|
||||
|
||||
const skipTests = params.skipTests.map(path => resolve(packages, path))
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
/// <reference types="vitest" />
|
||||
import { cpSync, existsSync, writeFileSync } from 'node:fs'
|
||||
import { relative, resolve } from 'node:path'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
// todo
|
||||
import { fumanBuild } from '@fuman/build/vite'
|
||||
import type { ConfigEnv, UserConfig } from 'vite'
|
||||
import { nodeExternals } from 'rollup-plugin-node-externals'
|
||||
import dts from 'vite-plugin-dts'
|
||||
|
||||
import { processPackageJson } from './vite-utils/package-json'
|
||||
|
||||
const rootDir = fileURLToPath(new URL('..', import.meta.url))
|
||||
|
||||
export default async (env: ConfigEnv): Promise<UserConfig> => {
|
||||
|
@ -16,15 +16,7 @@ export default async (env: ConfigEnv): Promise<UserConfig> => {
|
|||
throw new Error('This config is only for building')
|
||||
}
|
||||
|
||||
const { packageJson, entrypoints } = processPackageJson(process.cwd())
|
||||
|
||||
let customConfig: any
|
||||
try {
|
||||
const mod = await import(resolve(process.cwd(), 'build.config.js'))
|
||||
customConfig = await mod.default()
|
||||
} catch (e) {
|
||||
if (e.code !== 'ERR_MODULE_NOT_FOUND') throw e
|
||||
}
|
||||
const packageJson = JSON.parse(readFileSync(join(rootDir, 'package.json'), 'utf8'))
|
||||
|
||||
const CJS_DEPRECATION_WARNING = `
|
||||
if (typeof globalThis !== 'undefined' && !globalThis._MTCUTE_CJS_DEPRECATION_WARNED) {
|
||||
|
@ -34,69 +26,19 @@ if (typeof globalThis !== 'undefined' && !globalThis._MTCUTE_CJS_DEPRECATION_WAR
|
|||
}
|
||||
`.trim()
|
||||
|
||||
if (customConfig?.preBuild) {
|
||||
await customConfig.preBuild()
|
||||
}
|
||||
|
||||
return {
|
||||
build: {
|
||||
rollupOptions: {
|
||||
plugins: [
|
||||
...(customConfig?.rollupPluginsPre ?? []),
|
||||
nodeExternals({
|
||||
builtinsPrefix: 'ignore',
|
||||
exclude: /^@fuman\//,
|
||||
}),
|
||||
{
|
||||
name: 'mtcute-finalize',
|
||||
name: 'mtcute-cjs-deprecated',
|
||||
renderChunk(code, chunk, options) {
|
||||
if (options.format !== 'cjs') return null
|
||||
|
||||
return `${CJS_DEPRECATION_WARNING}\n${code}`
|
||||
},
|
||||
async closeBundle() {
|
||||
const packageDir = process.cwd()
|
||||
const outDir = resolve(packageDir, 'dist')
|
||||
|
||||
customConfig?.finalPackageJson?.(packageJson)
|
||||
|
||||
writeFileSync(resolve(outDir, 'package.json'), JSON.stringify(packageJson, null, 4))
|
||||
cpSync(resolve(rootDir, 'LICENSE'), resolve(outDir, 'LICENSE'))
|
||||
cpSync(resolve(process.cwd(), 'README.md'), resolve(outDir, 'README.md'))
|
||||
|
||||
if (existsSync(resolve(outDir, 'chunks/cjs'))) {
|
||||
// write {"type":"commonjs"} into chunks/cjs so that node doesn't complain
|
||||
const cjsFile = resolve(outDir, 'chunks/cjs/package.json')
|
||||
writeFileSync(cjsFile, JSON.stringify({ type: 'commonjs' }, null, 4))
|
||||
}
|
||||
|
||||
for (const [name, entry] of Object.entries(entrypoints)) {
|
||||
const dTsFile = resolve(outDir, `${name}.d.ts`)
|
||||
if (!existsSync(dTsFile)) {
|
||||
const entryTypings = resolve(outDir, entry.replace('/src/', '/').replace(/\.ts$/, '.d.ts'))
|
||||
if (!existsSync(entryTypings)) continue
|
||||
|
||||
const relativePath = relative(outDir, entryTypings)
|
||||
writeFileSync(dTsFile, `export * from './${relativePath.replace(/\.d\.ts$/, '.js')}'`)
|
||||
}
|
||||
|
||||
cpSync(dTsFile, dTsFile.replace(/\.d\.ts$/, '.d.cts'))
|
||||
}
|
||||
|
||||
await customConfig?.final?.({ outDir, packageDir })
|
||||
},
|
||||
},
|
||||
...(customConfig?.rollupPluginsPost ?? []),
|
||||
],
|
||||
output: {
|
||||
minifyInternalExports: false,
|
||||
chunkFileNames: 'chunks/[format]/[hash].js',
|
||||
},
|
||||
external: customConfig?.external,
|
||||
},
|
||||
lib: {
|
||||
entry: entrypoints as any,
|
||||
formats: customConfig?.buildCjs === false ? ['es'] : ['es', 'cjs'],
|
||||
},
|
||||
minify: false,
|
||||
outDir: 'dist',
|
||||
|
@ -104,10 +46,17 @@ if (typeof globalThis !== 'undefined' && !globalThis._MTCUTE_CJS_DEPRECATION_WAR
|
|||
target: 'es2022',
|
||||
},
|
||||
plugins: [
|
||||
...(customConfig?.vitePlugins ?? []),
|
||||
nodeExternals({
|
||||
builtinsPrefix: 'ignore',
|
||||
}),
|
||||
fumanBuild({
|
||||
root: rootDir,
|
||||
autoSideEffectsFalse: true,
|
||||
}),
|
||||
dts({
|
||||
// broken; see https://github.com/qmhc/vite-plugin-dts/issues/321, https://github.com/microsoft/rushstack/issues/3557
|
||||
// rollupTypes: true,
|
||||
insertTypesEntry: true,
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable no-restricted-globals */
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
|
@ -78,15 +77,6 @@ export default defineConfig({
|
|||
return code
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'fix-events',
|
||||
transform(code) {
|
||||
if (!code.includes('events')) return code
|
||||
return code.replace(/^import (.+?) from ['"]events['"]/gms, (_, name) => {
|
||||
return `import ${name} from 'node:events'`
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'fix-wasm-load',
|
||||
async transform(code) {
|
||||
|
|
|
@ -6,7 +6,6 @@ import { fixupCjs } from './vite-utils/fixup-cjs'
|
|||
import { testSetup } from './vite-utils/test-setup-plugin'
|
||||
import { collectTestEntrypoints } from './vite-utils/collect-test-entrypoints'
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const POLYFILLS = resolve(__dirname, 'vite-utils/polyfills-deno.ts')
|
||||
|
||||
export default defineConfig({
|
||||
|
|
1
.npmrc
1
.npmrc
|
@ -1 +1,2 @@
|
|||
@jsr:registry=https://npm.jsr.io
|
||||
@fuman:registry=https://npm.tei.su
|
||||
|
|
68
build.config.js
Normal file
68
build.config.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
/** @type {import('@fuman/build').RootConfig} */
|
||||
export default {
|
||||
jsr: {
|
||||
exclude: ['**/*.{test,bench,test-utils}.ts', '**/__fixtures__/**'],
|
||||
sourceDir: 'src',
|
||||
transformCode: (path, code) => {
|
||||
// add shims for node-specific APIs and replace NodeJS.* types
|
||||
// pretty fragile, but it works for now
|
||||
// todo: remove this god awfulness and use `declare const` in-place instead
|
||||
|
||||
const typesToReplace = {
|
||||
'NodeJS\\.Timeout': 'number',
|
||||
'NodeJS\\.Immediate': 'number',
|
||||
}
|
||||
const nodeSpecificApis = {
|
||||
setImmediate: '(cb: (...args: any[]) => void, ...args: any[]) => number',
|
||||
clearImmediate: '(id: number) => void',
|
||||
Buffer:
|
||||
'{ '
|
||||
+ 'concat: (...args: any[]) => Uint8Array, '
|
||||
+ 'from: (data: any, encoding?: string) => { toString(encoding?: string): string }, '
|
||||
+ ' }',
|
||||
SharedWorker: ['type', 'never'],
|
||||
WorkerGlobalScope:
|
||||
'{ '
|
||||
+ ' new (): typeof WorkerGlobalScope, '
|
||||
+ ' postMessage: (message: any, transfer?: Transferable[]) => void, '
|
||||
+ ' addEventListener: (type: "message", listener: (ev: MessageEvent) => void) => void, '
|
||||
+ ' }',
|
||||
process: '{ ' + 'hrtime: { bigint: () => bigint }, ' + '}',
|
||||
}
|
||||
|
||||
for (const [name, decl_] of Object.entries(nodeSpecificApis)) {
|
||||
if (code.includes(name)) {
|
||||
if (name === 'Buffer' && code.includes('node:buffer')) continue
|
||||
|
||||
const isType = Array.isArray(decl_) && decl_[0] === 'type'
|
||||
const decl = isType ? decl_[1] : decl_
|
||||
|
||||
if (isType) {
|
||||
code = `declare type ${name} = ${decl};\n${code}`
|
||||
} else {
|
||||
code = `declare const ${name}: ${decl};\n${code}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const [oldType, newType] of Object.entries(typesToReplace)) {
|
||||
if (code.match(oldType)) {
|
||||
code = code.replace(new RegExp(oldType, 'g'), newType)
|
||||
}
|
||||
}
|
||||
|
||||
return code
|
||||
},
|
||||
},
|
||||
versioning: {
|
||||
exclude: [
|
||||
'**/*.test.ts',
|
||||
'**/*.test-utils.ts',
|
||||
'**/__fixtures__/**',
|
||||
'**/*.md',
|
||||
'typedoc.cjs',
|
||||
'{scripts,dist,tests,private}/**',
|
||||
],
|
||||
},
|
||||
viteConfig: '.config/vite.build.ts',
|
||||
}
|
|
@ -87,6 +87,7 @@ export default antfu({
|
|||
'ts/switch-exhaustiveness-check': 'off',
|
||||
'ts/restrict-template-expressions': 'off',
|
||||
'ts/method-signature-style': 'off',
|
||||
'style/indent-binary-ops': 'off',
|
||||
},
|
||||
}, {
|
||||
ignores: [
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"homepage": "https://mtcute.dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mtcute/mtcute"
|
||||
"url": "git+https://github.com/mtcute/mtcute.git"
|
||||
},
|
||||
"keywords": [
|
||||
"telegram",
|
||||
|
@ -26,7 +26,7 @@
|
|||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"postinstall": "node scripts/validate-deps-versions.js && node scripts/remove-jsr-sourcefiles.js",
|
||||
"postinstall": "fuman-build validate-workspace-deps && node scripts/remove-jsr-sourcefiles.js",
|
||||
"test": "vitest --config .config/vite.ts run",
|
||||
"test:dev": "vitest --config .config/vite.ts watch",
|
||||
"test:ui": "vitest --config .config/vite.ts --ui",
|
||||
|
@ -42,12 +42,13 @@
|
|||
"lint:fix": "eslint --fix .",
|
||||
"publish-all": "node scripts/publish.js all",
|
||||
"docs": "typedoc --options .config/typedoc/config.cjs",
|
||||
"build-package": "node scripts/build-package.js",
|
||||
"build-package": "tsx scripts/build-package.js",
|
||||
"build-package-vite": "node scripts/build-package-vite.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "2.26.0",
|
||||
"@fuman/jsr": "workspace:^",
|
||||
"@fuman/build": "0.0.1",
|
||||
"@fuman/utils": "0.0.1",
|
||||
"@types/deno": "npm:@teidesu/deno-types@1.46.3",
|
||||
"@types/node": "20.10.0",
|
||||
"@types/ws": "8.5.4",
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
/** @type {import('@fuman/build/vite').CustomBuildConfig} */
|
||||
export default () => ({
|
||||
buildCjs: false,
|
||||
external: ['bun', 'bun:sqlite'],
|
||||
viteConfig: {
|
||||
build: {
|
||||
lib: {
|
||||
formats: ['es'],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['bun', 'bun:sqlite'],
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -12,20 +12,20 @@
|
|||
"./utils.js": "./src/utils.ts",
|
||||
"./methods.js": "./src/methods.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"docs": "typedoc",
|
||||
"build": "pnpm run -w build-package bun"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mtcute/core": "workspace:^",
|
||||
"@mtcute/html-parser": "workspace:^",
|
||||
"@mtcute/markdown-parser": "workspace:^",
|
||||
"@mtcute/wasm": "workspace:^",
|
||||
"@fuman/bun": "workspace:^",
|
||||
"@fuman/net": "workspace:^",
|
||||
"@fuman/io": "workspace:^"
|
||||
"@fuman/utils": "0.0.1",
|
||||
"@fuman/bun": "0.0.1",
|
||||
"@fuman/net": "0.0.1",
|
||||
"@fuman/io": "0.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mtcute/test": "workspace:^"
|
||||
},
|
||||
"fuman": {
|
||||
"jsr": "skip"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
BaseTelegramClient as BaseTelegramClientBase,
|
||||
TelegramClient as TelegramClientBase,
|
||||
} from '@mtcute/core/client.js'
|
||||
import { unknownToError } from '@fuman/utils'
|
||||
|
||||
import { downloadToFile } from './methods/download-file.js'
|
||||
import { downloadAsNodeStream } from './methods/download-node-stream.js'
|
||||
|
@ -127,7 +128,7 @@ export class TelegramClient extends TelegramClientBase {
|
|||
|
||||
this.start(params)
|
||||
.then(then)
|
||||
.catch(err => this.emitError(err))
|
||||
.catch(err => this.onError.emit(unknownToError(err)))
|
||||
}
|
||||
|
||||
downloadToFile(
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
ige256Encrypt,
|
||||
initSync,
|
||||
} from '@mtcute/wasm'
|
||||
import { u8 } from '@fuman/utils'
|
||||
|
||||
// we currently prefer wasm for ctr because bun mostly uses browserify polyfills for node:crypto
|
||||
// which are slow AND semi-broken
|
||||
|
@ -52,20 +53,20 @@ export class BunCryptoProvider extends BaseCryptoProvider implements ICryptoProv
|
|||
algo = 'sha512',
|
||||
): Promise<Uint8Array> {
|
||||
return new Promise((resolve, reject) =>
|
||||
pbkdf2(password, salt, iterations, keylen, algo, (err: Error | null, buf: Uint8Array) =>
|
||||
err !== null ? reject(err) : resolve(buf)),
|
||||
pbkdf2(password, salt, iterations, keylen, algo, (err: Error | null, buf: Buffer) =>
|
||||
err !== null ? reject(err) : resolve(buf as unknown as Uint8Array)),
|
||||
)
|
||||
}
|
||||
|
||||
sha1(data: Uint8Array): Uint8Array {
|
||||
const res = new Uint8Array(Bun.SHA1.byteLength)
|
||||
const res = u8.alloc(Bun.SHA1.byteLength)
|
||||
Bun.SHA1.hash(data, res)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
sha256(data: Uint8Array): Uint8Array {
|
||||
const res = new Uint8Array(Bun.SHA256.byteLength)
|
||||
const res = u8.alloc(Bun.SHA256.byteLength)
|
||||
Bun.SHA256.hash(data, res)
|
||||
|
||||
return res
|
||||
|
@ -90,7 +91,7 @@ export class BunCryptoProvider extends BaseCryptoProvider implements ICryptoProv
|
|||
// telegram accepts both zlib and gzip, but zlib is faster and has less overhead, so we use it here
|
||||
return deflateSync(data, {
|
||||
maxOutputLength: maxSize,
|
||||
})
|
||||
}) as unknown as Uint8Array
|
||||
// hot path, avoid additional runtime checks
|
||||
} catch (e: any) {
|
||||
if (e.code === 'ERR_BUFFER_TOO_LARGE') {
|
||||
|
@ -102,7 +103,7 @@ export class BunCryptoProvider extends BaseCryptoProvider implements ICryptoProv
|
|||
}
|
||||
|
||||
gunzip(data: Uint8Array): Uint8Array {
|
||||
return gunzipSync(data)
|
||||
return gunzipSync(data) as unknown as Uint8Array
|
||||
}
|
||||
|
||||
randomFill(buf: Uint8Array): void {
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
TelegramWorkerPort as TelegramWorkerPortBase,
|
||||
} from '@mtcute/core/worker.js'
|
||||
|
||||
import { BunPlatform } from './platform'
|
||||
import { BunPlatform } from './platform.js'
|
||||
|
||||
export type { TelegramWorkerOptions, WorkerCustomMethods }
|
||||
|
||||
|
|
|
@ -8,13 +8,10 @@
|
|||
"license": "MIT",
|
||||
"sideEffects": false,
|
||||
"exports": "./src/index.ts",
|
||||
"scripts": {
|
||||
"build": "pnpm run -w build-package convert"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mtcute/core": "workspace:^",
|
||||
"@fuman/utils": "workspace:^",
|
||||
"@fuman/net": "workspace:^"
|
||||
"@fuman/utils": "0.0.1",
|
||||
"@fuman/net": "0.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mtcute/test": "workspace:^"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Long, MtArgumentError } from '@mtcute/core'
|
||||
import { base64, typed } from '@fuman/utils'
|
||||
import { base64, typed, u8 } from '@fuman/utils'
|
||||
|
||||
import type { PyrogramSession } from './types.js'
|
||||
|
||||
|
@ -13,32 +13,32 @@ export function serializePyrogramSession(session: PyrogramSession): string {
|
|||
|
||||
const userIdLong = Long.fromNumber(session.userId, true)
|
||||
|
||||
let u8: Uint8Array
|
||||
let buf: Uint8Array
|
||||
|
||||
if (session.apiId === undefined) {
|
||||
// old format
|
||||
u8 = new Uint8Array(SESSION_STRING_SIZE_OLD)
|
||||
const dv = typed.toDataView(u8)
|
||||
buf = u8.alloc(SESSION_STRING_SIZE_OLD)
|
||||
const dv = typed.toDataView(buf)
|
||||
|
||||
dv.setUint8(0, session.dcId)
|
||||
dv.setUint8(1, session.isTest ? 1 : 0)
|
||||
u8.set(session.authKey, 2)
|
||||
buf.set(session.authKey, 2)
|
||||
dv.setUint32(258, userIdLong.high)
|
||||
dv.setUint32(262, userIdLong.low)
|
||||
dv.setUint8(266, session.isBot ? 1 : 0)
|
||||
} else {
|
||||
u8 = new Uint8Array(SESSION_STRING_SIZE)
|
||||
const dv = typed.toDataView(u8)
|
||||
buf = u8.alloc(SESSION_STRING_SIZE)
|
||||
const dv = typed.toDataView(buf)
|
||||
|
||||
dv.setUint8(0, session.dcId)
|
||||
dv.setUint32(1, session.apiId)
|
||||
dv.setUint8(5, session.isTest ? 1 : 0)
|
||||
u8.set(session.authKey, 6)
|
||||
buf.set(session.authKey, 6)
|
||||
|
||||
dv.setUint32(262, userIdLong.high)
|
||||
dv.setUint32(266, userIdLong.low)
|
||||
dv.setUint8(270, session.isBot ? 1 : 0)
|
||||
}
|
||||
|
||||
return base64.encode(u8, true)
|
||||
return base64.encode(buf, true)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { MtArgumentError } from '@mtcute/core'
|
||||
import { base64, typed } from '@fuman/utils'
|
||||
import { base64, typed, u8 } from '@fuman/utils'
|
||||
import { ip } from '@fuman/net'
|
||||
|
||||
import type { TelethonSession } from './types.js'
|
||||
|
@ -10,26 +10,26 @@ export function serializeTelethonSession(session: TelethonSession): string {
|
|||
}
|
||||
|
||||
const ipSize = session.ipv6 ? 16 : 4
|
||||
const u8 = new Uint8Array(259 + ipSize)
|
||||
const dv = typed.toDataView(u8)
|
||||
const buf = u8.alloc(259 + ipSize)
|
||||
const dv = typed.toDataView(buf)
|
||||
|
||||
dv.setUint8(0, session.dcId)
|
||||
|
||||
let pos
|
||||
|
||||
if (session.ipv6) {
|
||||
u8.subarray(1, 17).set(ip.toBytesV6(ip.parseV6(session.ipAddress)))
|
||||
buf.subarray(1, 17).set(ip.toBytesV6(ip.parseV6(session.ipAddress)))
|
||||
pos = 17
|
||||
} else {
|
||||
u8.subarray(1, 5).set(ip.parseV4(session.ipAddress).parts)
|
||||
buf.subarray(1, 5).set(ip.parseV4(session.ipAddress).parts)
|
||||
pos = 5
|
||||
}
|
||||
|
||||
dv.setUint16(pos, session.port)
|
||||
pos += 2
|
||||
u8.set(session.authKey, pos)
|
||||
buf.set(session.authKey, pos)
|
||||
|
||||
let b64 = base64.encode(u8, true)
|
||||
let b64 = base64.encode(buf, true)
|
||||
while (b64.length % 4 !== 0) b64 += '=' // for some reason telethon uses padding
|
||||
|
||||
return `1${b64}`
|
||||
|
|
|
@ -5,6 +5,7 @@ import * as fs from 'node:fs'
|
|||
|
||||
const KNOWN_DECORATORS = ['memoizeGetters', 'makeInspectable']
|
||||
|
||||
/** @type {import('@fuman/build/vite').CustomBuildConfig} */
|
||||
export default () => {
|
||||
const networkManagerId = fileURLToPath(new URL('./src/network/network-manager.ts', import.meta.url))
|
||||
const highlevelTypesDir = fileURLToPath(new URL('./src/highlevel/types', import.meta.url))
|
||||
|
@ -18,7 +19,7 @@ export default () => {
|
|||
)
|
||||
|
||||
return {
|
||||
rollupPluginsPre: [
|
||||
pluginsPre: [
|
||||
{
|
||||
name: 'mtcute-core-build-plugin',
|
||||
transform(code, id) {
|
||||
|
@ -70,7 +71,7 @@ export default () => {
|
|||
},
|
||||
},
|
||||
],
|
||||
finalJsr({ outDir }) {
|
||||
finalizeJsr({ outDir }) {
|
||||
const networkMgrFile = resolve(outDir, 'network/network-manager.ts')
|
||||
const code = fs.readFileSync(networkMgrFile, 'utf8')
|
||||
|
||||
|
|
|
@ -15,14 +15,13 @@
|
|||
"./methods.js": "./src/highlevel/methods.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "pnpm run -w build-package core",
|
||||
"gen-client": "node ./scripts/generate-client.cjs",
|
||||
"gen-updates": "node ./scripts/generate-updates.cjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fuman/io": "workspace:^",
|
||||
"@fuman/net": "workspace:^",
|
||||
"@fuman/utils": "workspace:^",
|
||||
"@fuman/io": "0.0.1",
|
||||
"@fuman/net": "0.0.1",
|
||||
"@fuman/utils": "0.0.1",
|
||||
"@mtcute/file-id": "workspace:^",
|
||||
"@mtcute/tl": "workspace:^",
|
||||
"@mtcute/tl-runtime": "workspace:^",
|
||||
|
|
|
@ -579,8 +579,10 @@ withParams(params: RpcCallOptions): this\n`)
|
|||
it.type = { kind: ts.SyntaxKind.StringKeyword }
|
||||
} else if (
|
||||
it.initializer.kind === ts.SyntaxKind.NumericLiteral
|
||||
|| (it.initializer.kind === ts.SyntaxKind.Identifier
|
||||
&& (it.initializer.escapedText === 'NaN' || it.initializer.escapedText === 'Infinity'))
|
||||
|| (
|
||||
it.initializer.kind === ts.SyntaxKind.Identifier
|
||||
&& (it.initializer.escapedText === 'NaN' || it.initializer.escapedText === 'Infinity')
|
||||
)
|
||||
) {
|
||||
it.type = { kind: ts.SyntaxKind.NumberKeyword }
|
||||
} else {
|
||||
|
@ -723,8 +725,6 @@ withParams(params: RpcCallOptions): this\n`)
|
|||
'call',
|
||||
'importSession',
|
||||
'exportSession',
|
||||
'onError',
|
||||
'emitError',
|
||||
'handleClientUpdate',
|
||||
'getApiCrenetials',
|
||||
'getPoolSize',
|
||||
|
|
|
@ -8,7 +8,7 @@ describe('BaseTelegramClient', () => {
|
|||
const session = await client.exportSession()
|
||||
|
||||
expect(session).toMatchInlineSnapshot(
|
||||
`"AwQAAAAXAgIADjE0OS4xNTQuMTY3LjUwALsBAAAXAgICDzE0OS4xNTQuMTY3LjIyMrsBAAD-AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"`,
|
||||
'"AwQAAAAXAgIADjE0OS4xNTQuMTY3LjUwALsBAAAXAgICDzE0OS4xNTQuMTY3LjIyMrsBAAD-AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -290,18 +290,7 @@ export class BaseTelegramClient implements ITelegramClient {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an error handler for the client
|
||||
*
|
||||
* @param handler Error handler.
|
||||
*/
|
||||
onError(handler: (err: unknown) => void): void {
|
||||
this.mt.onError(handler)
|
||||
}
|
||||
|
||||
emitError(err: unknown): void {
|
||||
this.mt.emitError(err)
|
||||
}
|
||||
onError: Emitter<Error> = new Emitter()
|
||||
|
||||
handleClientUpdate(updates: tl.TypeUpdates, noDispatch?: boolean): void {
|
||||
this.updates?.handleClientUpdate(updates, noDispatch)
|
||||
|
|
|
@ -6584,12 +6584,6 @@ TelegramClient.prototype.importSession = function (...args) {
|
|||
TelegramClient.prototype.exportSession = function (...args) {
|
||||
return this._client.exportSession(...args)
|
||||
}
|
||||
TelegramClient.prototype.onError = function (...args) {
|
||||
return this._client.onError(...args)
|
||||
}
|
||||
TelegramClient.prototype.emitError = function (...args) {
|
||||
return this._client.emitError(...args)
|
||||
}
|
||||
TelegramClient.prototype.handleClientUpdate = function (...args) {
|
||||
return this._client.handleClientUpdate(...args)
|
||||
}
|
||||
|
|
|
@ -52,13 +52,12 @@ export interface ITelegramClient {
|
|||
): Promise<tl.RpcCallReturn[T['_']]>
|
||||
importSession(session: string | StringSessionData, force?: boolean): Promise<void>
|
||||
exportSession(): Promise<string>
|
||||
onError(handler: (err: unknown) => void): void
|
||||
emitError(err: unknown): void
|
||||
handleClientUpdate(updates: tl.TypeUpdates, noDispatch?: boolean): void
|
||||
|
||||
onServerUpdate: Emitter<tl.TypeUpdates>
|
||||
onRawUpdate: Emitter<RawUpdateInfo>
|
||||
onConnectionState: Emitter<ConnectionState>
|
||||
onError: Emitter<Error>
|
||||
|
||||
getApiCrenetials(): Promise<{ id: number, hash: string }>
|
||||
// todo - this is only used for file dl/ul, which should probably be moved
|
||||
|
|
|
@ -1,36 +1,41 @@
|
|||
import type { tl } from '@mtcute/tl'
|
||||
import { AsyncResource, asNonNull } from '@fuman/utils'
|
||||
|
||||
import { MtTypeAssertionError } from '../../types/errors.js'
|
||||
import { Reloadable } from '../../utils/reloadable.js'
|
||||
import { tlJsonToJson } from '../../utils/tl-json.js'
|
||||
import type { BaseTelegramClient } from '../base.js'
|
||||
import type { AppConfigSchema } from '../types/misc/app-config.js'
|
||||
|
||||
export class AppConfigManager {
|
||||
constructor(private client: BaseTelegramClient) {}
|
||||
private _resource
|
||||
constructor(private client: BaseTelegramClient) {
|
||||
this._resource = new AsyncResource<tl.help.RawAppConfig>({
|
||||
fetcher: async ({ current }) => {
|
||||
const res = await this.client.call({
|
||||
_: 'help.getAppConfig',
|
||||
hash: current?.hash ?? 0,
|
||||
})
|
||||
|
||||
private _reloadable = new Reloadable<tl.help.RawAppConfig>({
|
||||
reload: this._reload.bind(this),
|
||||
getExpiresAt: () => 3_600_000,
|
||||
disableAutoReload: true,
|
||||
})
|
||||
if (res._ === 'help.appConfigNotModified') {
|
||||
return {
|
||||
data: asNonNull(current),
|
||||
expiresIn: 3_600_000,
|
||||
}
|
||||
}
|
||||
|
||||
private async _reload(old?: tl.help.RawAppConfig) {
|
||||
const res = await this.client.call({
|
||||
_: 'help.getAppConfig',
|
||||
hash: old?.hash ?? 0,
|
||||
return {
|
||||
data: res,
|
||||
expiresIn: 3_600_000,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
if (res._ === 'help.appConfigNotModified') return old!
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
private _object?: AppConfigSchema
|
||||
async get(): Promise<AppConfigSchema> {
|
||||
if (!this._reloadable.isStale && this._object) return this._object
|
||||
if (!this._resource.isStale && this._object) return this._object
|
||||
|
||||
const obj = tlJsonToJson((await this._reloadable.get()).config)
|
||||
const obj = tlJsonToJson((await this._resource.get()).config)
|
||||
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
throw new MtTypeAssertionError('appConfig', 'object', typeof obj)
|
||||
|
|
|
@ -67,6 +67,7 @@ function _initializeClient(this: TelegramClient, opts: TelegramClientOptions) {
|
|||
Object.defineProperty(this, 'onServerUpdate', { value: this._client.onServerUpdate })
|
||||
Object.defineProperty(this, 'onRawUpdate', { value: this._client.onServerUpdate })
|
||||
Object.defineProperty(this, 'onConnectionState', { value: this._client.onConnectionState })
|
||||
Object.defineProperty(this, 'onError', { value: this._client.onError })
|
||||
|
||||
if (!opts.disableUpdates) {
|
||||
const skipConversationUpdates = opts.skipConversationUpdates ?? true
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { unknownToError } from '@fuman/utils'
|
||||
|
||||
import type { ITelegramClient } from '../../client.types.js'
|
||||
import type { User } from '../../types/index.js'
|
||||
|
||||
|
@ -22,5 +24,5 @@ export function run(
|
|||
): void {
|
||||
start(client, params)
|
||||
.then(then)
|
||||
.catch(err => client.emitError(err))
|
||||
.catch(err => client.onError.emit(unknownToError(err)))
|
||||
}
|
||||
|
|
|
@ -4,10 +4,11 @@ import { Deferred, base64 } from '@fuman/utils'
|
|||
import type { MaybePromise } from '../../../types/utils.js'
|
||||
import { sleepWithAbort } from '../../../utils/misc-utils.js'
|
||||
import { assertTypeIs } from '../../../utils/type-assertions.js'
|
||||
import type { ITelegramClient, ServerUpdateHandler } from '../../client.types.js'
|
||||
import type { ITelegramClient } from '../../client.types.js'
|
||||
import type { MaybeDynamic } from '../../types/index.js'
|
||||
import { User } from '../../types/index.js'
|
||||
import { resolveMaybeDynamic } from '../../utils/misc-utils.js'
|
||||
import type { RawUpdateInfo } from '../../updates/types.js'
|
||||
|
||||
import { checkPassword } from './check-password.js'
|
||||
|
||||
|
@ -53,26 +54,19 @@ export async function signInQr(
|
|||
|
||||
let waiter: Deferred<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') {
|
||||
// todo: we should probably make this into an await-able function
|
||||
const onUpdate = ({ update }: RawUpdateInfo) => {
|
||||
if (update._ === 'updateLoginToken') {
|
||||
onQrScanned?.()
|
||||
waiter?.resolve()
|
||||
client.onServerUpdate(originalHandler)
|
||||
client.onRawUpdate.remove(onUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
client.onServerUpdate(onUpdate)
|
||||
client.onRawUpdate.add(onUpdate)
|
||||
|
||||
abortSignal?.addEventListener('abort', () => {
|
||||
client.onServerUpdate(originalHandler)
|
||||
client.onRawUpdate.remove(onUpdate)
|
||||
waiter?.reject(abortSignal.reason)
|
||||
})
|
||||
|
||||
|
@ -180,6 +174,6 @@ export async function signInQr(
|
|||
|
||||
return new User(self)
|
||||
} finally {
|
||||
client.onServerUpdate(originalHandler)
|
||||
client.onRawUpdate.remove(onUpdate)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { tl } from '@mtcute/tl'
|
||||
import type { IReadable } from '@fuman/io'
|
||||
import { read } from '@fuman/io'
|
||||
import { read, webReadableToFuman } from '@fuman/io'
|
||||
import { AsyncLock } from '@fuman/utils'
|
||||
|
||||
import { MtArgumentError } from '../../../types/errors.js'
|
||||
|
@ -175,7 +175,7 @@ export async function uploadFile(
|
|||
}
|
||||
|
||||
if (file instanceof ReadableStream) {
|
||||
file = read.async.fromWeb(file)
|
||||
file = webReadableToFuman(file)
|
||||
} else if (!(typeof file === 'object' && 'read' in file)) { // IReadable
|
||||
throw new MtArgumentError('Could not convert input `file` to stream!')
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { tl } from '@mtcute/tl'
|
||||
import { TlBinaryReader, TlBinaryWriter, TlSerializationCounter } from '@mtcute/tl-runtime'
|
||||
import { u8 } from '@fuman/utils'
|
||||
|
||||
import type { IKeyValueRepository } from '../../../storage/repository/key-value.js'
|
||||
import type { ServiceOptions } from '../../../storage/service/base.js'
|
||||
|
@ -18,7 +19,7 @@ export interface CurrentUserInfo {
|
|||
const KV_CURRENT_USER = 'current_user'
|
||||
|
||||
function serialize(info: CurrentUserInfo | null): Uint8Array {
|
||||
if (!info) return new Uint8Array(0)
|
||||
if (!info) return u8.alloc(0)
|
||||
|
||||
const hasUsernames = info.usernames.length > 0
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { typed } from '@fuman/utils'
|
||||
import { typed, u8 } from '@fuman/utils'
|
||||
|
||||
import type { IKeyValueRepository } from '../../../storage/repository/key-value.js'
|
||||
import type { ServiceOptions } from '../../../storage/service/base.js'
|
||||
|
@ -28,7 +28,7 @@ export class UpdatesStateService extends BaseService {
|
|||
}
|
||||
|
||||
private async _setInt(key: string, val: number): Promise<void> {
|
||||
const buf = new Uint8Array(4)
|
||||
const buf = u8.alloc(4)
|
||||
typed.toDataView(buf).setInt32(0, val, true)
|
||||
|
||||
await this._kv.set(key, buf)
|
||||
|
@ -73,7 +73,7 @@ export class UpdatesStateService extends BaseService {
|
|||
}
|
||||
|
||||
async setChannelPts(channelId: number, pts: number): Promise<void> {
|
||||
const buf = new Uint8Array(4)
|
||||
const buf = u8.alloc(4)
|
||||
typed.toDataView(buf).setUint32(0, pts, true)
|
||||
|
||||
await this._kv.set(KV_CHANNEL_PREFIX + channelId, buf)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { tl } from '@mtcute/tl'
|
||||
import { AsyncLock, Deferred, Deque, timers } from '@fuman/utils'
|
||||
import { AsyncLock, Deferred, Deque, timers, unknownToError } from '@fuman/utils'
|
||||
|
||||
import { MtArgumentError, MtTimeoutError } from '../../types/errors.js'
|
||||
import type { MaybePromise } from '../../types/utils.js'
|
||||
|
@ -564,7 +564,7 @@ export class Conversation {
|
|||
this._queuedNewMessage.popFront()
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
this.client.emitError(e)
|
||||
this.client.onError.emit(unknownToError(e))
|
||||
}
|
||||
|
||||
this._lastMessage = this._lastReceivedMessage = msg.id
|
||||
|
@ -594,7 +594,7 @@ export class Conversation {
|
|||
this._pendingEditMessage.delete(msg.id)
|
||||
}
|
||||
})().catch((e) => {
|
||||
this.client.emitError(e)
|
||||
this.client.onError.emit(unknownToError(e))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { tl } from '@mtcute/tl'
|
||||
import Long from 'long'
|
||||
import { AsyncLock, ConditionVariable, Deque, timers } from '@fuman/utils'
|
||||
import { AsyncLock, ConditionVariable, Deque, timers, unknownToError } from '@fuman/utils'
|
||||
|
||||
import { MtArgumentError } from '../../types/errors.js'
|
||||
import type { MaybePromise } from '../../types/utils.js'
|
||||
|
@ -195,7 +195,7 @@ export class UpdatesManager {
|
|||
|
||||
notifyLoggedIn(self: CurrentUserInfo): void {
|
||||
this.auth = self
|
||||
this.startLoop().catch(err => this.client.emitError(err))
|
||||
this.startLoop().catch(err => this.client.onError.emit(unknownToError(err)))
|
||||
}
|
||||
|
||||
notifyLoggedOut(): void {
|
||||
|
@ -238,7 +238,7 @@ export class UpdatesManager {
|
|||
this.updatesLoopActive = true
|
||||
timers.clearInterval(this.keepAliveInterval)
|
||||
this.keepAliveInterval = timers.setInterval(this._onKeepAlive, KEEP_ALIVE_INTERVAL)
|
||||
this._loop().catch(err => this.client.emitError(err))
|
||||
this._loop().catch(err => this.client.onError.emit(unknownToError(err)))
|
||||
|
||||
if (this.catchUpOnStart) {
|
||||
this.catchUp()
|
||||
|
@ -1245,20 +1245,20 @@ export class UpdatesManager {
|
|||
// we just needed to apply new pts values
|
||||
return
|
||||
case 'updateDcOptions': {
|
||||
const config = client.mt.network.config.getNow()
|
||||
const config = client.mt.network.config.getCached()
|
||||
|
||||
if (config) {
|
||||
client.mt.network.config.setData({
|
||||
...config,
|
||||
dcOptions: upd.dcOptions,
|
||||
})
|
||||
}, config.expires * 1000)
|
||||
} else {
|
||||
client.mt.network.config.update(true).catch(err => client.emitError(err))
|
||||
client.mt.network.config.update(true).catch(err => client.onError.emit(unknownToError(err)))
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'updateConfig':
|
||||
client.mt.network.config.update(true).catch(err => client.emitError(err))
|
||||
client.mt.network.config.update(true).catch(err => client.onError.emit(unknownToError(err)))
|
||||
break
|
||||
case 'updateUserName':
|
||||
// todo
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { typed } from '@fuman/utils'
|
||||
import { typed, u8 } from '@fuman/utils'
|
||||
|
||||
/**
|
||||
* Decode 5-bit encoded voice message waveform into
|
||||
|
@ -49,7 +49,7 @@ export function decodeWaveform(wf: Uint8Array): number[] {
|
|||
export function encodeWaveform(wf: number[]): Uint8Array {
|
||||
const bitsCount = wf.length * 5
|
||||
const bytesCount = ~~((bitsCount + 7) / 8)
|
||||
const result = new Uint8Array(bytesCount + 1)
|
||||
const result = u8.alloc(bytesCount + 1)
|
||||
const dv = typed.toDataView(result)
|
||||
|
||||
// Write each 0-31 unsigned char as 5 bit to result.
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
import type { tl } from '@mtcute/tl'
|
||||
import { Emitter } from '@fuman/utils'
|
||||
|
||||
import type { RpcCallOptions } from '../../network/network-manager.js'
|
||||
import type { MustEqual } from '../../types/utils.js'
|
||||
import { LogManager } from '../../utils/logger.js'
|
||||
import type { ConnectionState, ITelegramClient, ServerUpdateHandler } from '../client.types.js'
|
||||
import type { ConnectionState, ITelegramClient } 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 type { RawUpdateInfo } from '../updates/types.js'
|
||||
|
||||
import { AppConfigManagerProxy } from './app-config.js'
|
||||
import { WorkerInvoker } from './invoker.js'
|
||||
import type { ClientMessageHandler, SendFn, SomeWorker, WorkerCustomMethods } from './protocol.js'
|
||||
import { deserializeResult } from './protocol.js'
|
||||
import { TelegramStorageProxy } from './storage.js'
|
||||
import { deserializeError } from './errors.js'
|
||||
|
||||
export interface TelegramWorkerPortOptions {
|
||||
worker: SomeWorker
|
||||
|
@ -104,33 +106,10 @@ export abstract class TelegramWorkerPort<Custom extends WorkerCustomMethods> imp
|
|||
|
||||
abstract connectToWorker(worker: SomeWorker, handler: ClientMessageHandler): [SendFn, () => void]
|
||||
|
||||
private _serverUpdatesHandler: ServerUpdateHandler = () => {}
|
||||
onServerUpdate(handler: ServerUpdateHandler): void {
|
||||
this._serverUpdatesHandler = handler
|
||||
}
|
||||
|
||||
getServerUpdateHandler(): ServerUpdateHandler {
|
||||
return this._serverUpdatesHandler
|
||||
}
|
||||
|
||||
private _errorHandler: (err: unknown) => void = () => {}
|
||||
onError(handler: (err: unknown) => void): void {
|
||||
this._errorHandler = handler
|
||||
}
|
||||
|
||||
emitError(err: unknown): void {
|
||||
this._errorHandler(err)
|
||||
}
|
||||
|
||||
private _updateHandler: RawUpdateHandler = () => {}
|
||||
onUpdate(handler: RawUpdateHandler): void {
|
||||
this._updateHandler = handler
|
||||
}
|
||||
|
||||
private _connectionStateHandler: (state: ConnectionState) => void = () => {}
|
||||
onConnectionState(handler: (state: ConnectionState) => void): void {
|
||||
this._connectionStateHandler = handler
|
||||
}
|
||||
onServerUpdate: Emitter<tl.TypeUpdates> = new Emitter()
|
||||
onRawUpdate: Emitter<RawUpdateInfo> = new Emitter()
|
||||
onConnectionState: Emitter<ConnectionState> = new Emitter()
|
||||
onError: Emitter<Error> = new Emitter()
|
||||
|
||||
private _onMessage: ClientMessageHandler = (message) => {
|
||||
switch (message.type) {
|
||||
|
@ -138,22 +117,22 @@ export abstract class TelegramWorkerPort<Custom extends WorkerCustomMethods> imp
|
|||
this.log.handler(message.color, message.level, message.tag, message.fmt, message.args)
|
||||
break
|
||||
case 'server_update':
|
||||
this._serverUpdatesHandler(deserializeResult(message.update))
|
||||
this.onServerUpdate.emit(deserializeResult(message.update))
|
||||
break
|
||||
case 'conn_state':
|
||||
this._connectionStateHandler(message.state)
|
||||
this.onConnectionState.emit(message.state)
|
||||
break
|
||||
case 'update': {
|
||||
const peers = new PeersIndex(deserializeResult(message.users), deserializeResult(message.chats))
|
||||
peers.hasMin = message.hasMin
|
||||
this._updateHandler(deserializeResult(message.update), peers)
|
||||
this.onRawUpdate.emit({ update: deserializeResult(message.update), peers })
|
||||
break
|
||||
}
|
||||
case 'result':
|
||||
this._invoker.handleResult(message)
|
||||
break
|
||||
case 'error':
|
||||
this.emitError(message.error)
|
||||
this.onError.emit(deserializeError(message.error))
|
||||
break
|
||||
case 'stop':
|
||||
this._abortController.abort()
|
||||
|
|
|
@ -31,7 +31,7 @@ export type WorkerOutboundMessage =
|
|||
chats: SerializedResult<Map<number, tl.TypeChat>>
|
||||
hasMin: boolean
|
||||
}
|
||||
| { type: 'error', error: unknown }
|
||||
| { type: 'error', error: SerializedError }
|
||||
| { type: 'stop' }
|
||||
| { type: 'conn_state', state: ConnectionState }
|
||||
| {
|
||||
|
|
|
@ -55,13 +55,13 @@ export abstract class TelegramWorker<T extends WorkerCustomMethods> {
|
|||
fmt,
|
||||
args,
|
||||
})
|
||||
client.onError(err =>
|
||||
client.onError.add(err =>
|
||||
this.broadcast({
|
||||
type: 'error',
|
||||
error: err,
|
||||
error: serializeError(err),
|
||||
}),
|
||||
)
|
||||
client.onConnectionState(state =>
|
||||
client.onConnectionState.add(state =>
|
||||
this.broadcast({
|
||||
type: 'conn_state',
|
||||
state,
|
||||
|
@ -70,7 +70,7 @@ export abstract class TelegramWorker<T extends WorkerCustomMethods> {
|
|||
client.stopSignal.addEventListener('abort', () => this.broadcast({ type: 'stop' }))
|
||||
|
||||
if (client.updates) {
|
||||
client.onUpdate((update, peers) =>
|
||||
client.onRawUpdate.add(({ update, peers }) =>
|
||||
this.broadcast({
|
||||
type: 'update',
|
||||
update: serializeResult(update),
|
||||
|
@ -80,7 +80,7 @@ export abstract class TelegramWorker<T extends WorkerCustomMethods> {
|
|||
}),
|
||||
)
|
||||
} else {
|
||||
client.onServerUpdate(update =>
|
||||
client.onServerUpdate.add(update =>
|
||||
this.broadcast({
|
||||
type: 'server_update',
|
||||
update: serializeResult(update),
|
||||
|
|
|
@ -44,7 +44,7 @@ export class AuthKey {
|
|||
let padding = (16 /* header size */ + message.length + 12) /* min padding */ % 16
|
||||
padding = 12 + (padding ? 16 - padding : 0)
|
||||
|
||||
const buf = new Uint8Array(16 + message.length + padding)
|
||||
const buf = u8.alloc(16 + message.length + padding)
|
||||
const dv = typed.toDataView(buf)
|
||||
|
||||
dv.setInt32(0, serverSalt.low, true)
|
||||
|
|
|
@ -128,13 +128,13 @@ function rsaPad(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKey): Ui
|
|||
throw new MtArgumentError('Failed to pad: too big data')
|
||||
}
|
||||
|
||||
const dataPadded = new Uint8Array(192)
|
||||
const dataPadded = u8.alloc(192)
|
||||
dataPadded.set(data, 0)
|
||||
crypto.randomFill(dataPadded.subarray(data.length))
|
||||
data = dataPadded
|
||||
|
||||
for (;;) {
|
||||
const aesIv = new Uint8Array(32)
|
||||
const aesIv = u8.alloc(32)
|
||||
|
||||
const aesKey = crypto.randomBytes(32)
|
||||
|
||||
|
@ -417,7 +417,7 @@ export async function doAuthorization(
|
|||
}
|
||||
|
||||
if (dhGen._ === 'mt_dh_gen_retry') {
|
||||
const expectedHash = crypto.sha1(u8.concat3(newNonce, new Uint8Array([2]), authKeyAuxHash))
|
||||
const expectedHash = crypto.sha1(u8.concat3(newNonce, [2], authKeyAuxHash))
|
||||
|
||||
if (!typed.equal(expectedHash.subarray(4, 20), dhGen.newNonceHash2)) {
|
||||
throw new MtSecurityError('Step 4: invalid retry nonce hash from server')
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createStub } from '@mtcute/test'
|
||||
import type { tl } from '@mtcute/tl'
|
||||
import type { AsyncResourceContext } from '@fuman/utils'
|
||||
|
||||
import { ConfigManager } from './config-manager.js'
|
||||
|
||||
|
@ -9,13 +10,20 @@ describe('ConfigManager', () => {
|
|||
expires: 300,
|
||||
})
|
||||
const getConfig = vi.fn()
|
||||
const fakePerfNow = vi.fn(() => Date.now())
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
vi.setSystemTime(0)
|
||||
vi.stubGlobal('performance', {
|
||||
now: fakePerfNow,
|
||||
})
|
||||
getConfig.mockClear().mockImplementation(() => Promise.resolve(config))
|
||||
})
|
||||
afterEach(() => void vi.useRealTimers())
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
it('should fetch initial config', async () => {
|
||||
const cm = new ConfigManager(getConfig)
|
||||
|
@ -24,7 +32,7 @@ describe('ConfigManager', () => {
|
|||
|
||||
expect(getConfig).toHaveBeenCalledTimes(1)
|
||||
expect(fetchedConfig).toEqual(config)
|
||||
expect(cm.getNow()).toEqual(config)
|
||||
expect(cm.getCached()).toEqual(config)
|
||||
})
|
||||
|
||||
it('should automatically update config', async () => {
|
||||
|
@ -47,7 +55,7 @@ describe('ConfigManager', () => {
|
|||
const cm = new ConfigManager(getConfig)
|
||||
expect(cm.isStale).toBe(true)
|
||||
|
||||
cm.setData(config)
|
||||
cm.setData(config, config.expires * 1000)
|
||||
expect(cm.isStale).toBe(false)
|
||||
|
||||
vi.setSystemTime(300_000)
|
||||
|
@ -84,15 +92,23 @@ describe('ConfigManager', () => {
|
|||
it('should call listeners on config update', async () => {
|
||||
const cm = new ConfigManager(getConfig)
|
||||
const listener = vi.fn()
|
||||
cm.onReload(listener)
|
||||
cm.onUpdated.add(listener)
|
||||
|
||||
await cm.update()
|
||||
const call = structuredClone(listener.mock.calls[0][0]) as AsyncResourceContext<tl.RawConfig>
|
||||
|
||||
vi.setSystemTime(300_000)
|
||||
cm.onReload(listener)
|
||||
cm.onUpdated.remove(listener)
|
||||
await cm.update()
|
||||
|
||||
expect(listener).toHaveBeenCalledOnce()
|
||||
expect(listener).toHaveBeenCalledWith(config)
|
||||
expect(call).toEqual({
|
||||
abort: {},
|
||||
current: config,
|
||||
currentExpiresAt: 300_000,
|
||||
currentFetchedAt: 0,
|
||||
isBackground: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('should correctly destroy', async () => {
|
||||
|
|
|
@ -1,18 +1,24 @@
|
|||
import { AsyncResource } from '@fuman/utils'
|
||||
import type { tl } from '@mtcute/tl'
|
||||
|
||||
import { Reloadable } from '../utils/reloadable.js'
|
||||
|
||||
/**
|
||||
* Config manager is responsible for keeping
|
||||
* the current server configuration up-to-date
|
||||
* and providing methods to find the best DC
|
||||
* option for the current session.
|
||||
*/
|
||||
export class ConfigManager extends Reloadable<tl.RawConfig> {
|
||||
export class ConfigManager extends AsyncResource<tl.RawConfig> {
|
||||
constructor(update: () => Promise<tl.RawConfig>) {
|
||||
super({
|
||||
reload: update,
|
||||
getExpiresAt: data => data.expires * 1000,
|
||||
fetcher: async () => {
|
||||
const res = await update()
|
||||
|
||||
return {
|
||||
data: res,
|
||||
expiresIn: res.expires * 1000 - Date.now(),
|
||||
}
|
||||
},
|
||||
autoReload: true,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -26,7 +32,9 @@ export class ConfigManager extends Reloadable<tl.RawConfig> {
|
|||
}): Promise<tl.RawDcOption | undefined> {
|
||||
if (this.isStale) await this.update()
|
||||
|
||||
const options = this._data!.dcOptions.filter((opt) => {
|
||||
const data = this.getCached()!
|
||||
|
||||
const options = data.dcOptions.filter((opt) => {
|
||||
if (opt.tcpoOnly) return false // unsupported
|
||||
if (opt.ipv6 && !params.allowIpv6) return false
|
||||
if (opt.mediaOnly && !params.allowMedia) return false
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { tl } from '@mtcute/tl'
|
||||
import type { Middleware } from '@fuman/utils'
|
||||
|
||||
import type { Middleware } from '../../utils/composer.js'
|
||||
import type { RpcCallMiddleware, RpcCallMiddlewareContext } from '../network-manager.js'
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,7 +2,7 @@ import Long from 'long'
|
|||
import type { mtp, tl } from '@mtcute/tl'
|
||||
import type { TlBinaryWriter, TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime'
|
||||
import { TlSerializationCounter } from '@mtcute/tl-runtime'
|
||||
import { type Deferred, Deque, LruSet } from '@fuman/utils'
|
||||
import { type Deferred, Deque, LruSet, timers } from '@fuman/utils'
|
||||
|
||||
import { MtcuteError } from '../types/index.js'
|
||||
import type {
|
||||
|
@ -16,7 +16,6 @@ import {
|
|||
compareLongs,
|
||||
getRandomInt,
|
||||
randomLong,
|
||||
timers,
|
||||
} from '../utils/index.js'
|
||||
|
||||
import { AuthKey } from './auth-key.js'
|
||||
|
|
|
@ -47,32 +47,32 @@ function _isQuadraticResidue(a: bigint): boolean {
|
|||
}
|
||||
|
||||
function executeTlsOperations(h: TlsHelloWriter): void {
|
||||
h.string(new Uint8Array([0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0x01, 0xFC, 0x03, 0x03]))
|
||||
h.string([0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0x01, 0xFC, 0x03, 0x03])
|
||||
h.zero(32)
|
||||
h.string(new Uint8Array([0x20]))
|
||||
h.string([0x20])
|
||||
h.random(32)
|
||||
h.string(new Uint8Array([0x00, 0x20]))
|
||||
h.string([0x00, 0x20])
|
||||
h.grease(0)
|
||||
/* eslint-disable antfu/consistent-list-newline */
|
||||
h.string(new Uint8Array([
|
||||
h.string([
|
||||
0x13, 0x01, 0x13, 0x02, 0x13, 0x03, 0xC0, 0x2B, 0xC0, 0x2F, 0xC0, 0x2C,
|
||||
0xC0, 0x30, 0xCC, 0xA9, 0xCC, 0xA8, 0xC0, 0x13, 0xC0, 0x14, 0x00, 0x9C,
|
||||
0x00, 0x9D, 0x00, 0x2F, 0x00, 0x35, 0x01, 0x00, 0x01, 0x93,
|
||||
]))
|
||||
])
|
||||
h.grease(2)
|
||||
h.string(new Uint8Array([0x00, 0x00, 0x00, 0x00]))
|
||||
h.string([0x00, 0x00, 0x00, 0x00])
|
||||
h.beginScope()
|
||||
h.beginScope()
|
||||
h.string(new Uint8Array([0x00]))
|
||||
h.string([0x00])
|
||||
h.beginScope()
|
||||
h.domain()
|
||||
h.endScope()
|
||||
h.endScope()
|
||||
h.endScope()
|
||||
h.string(new Uint8Array([0x00, 0x17, 0x00, 0x00, 0xFF, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x08]))
|
||||
h.string([0x00, 0x17, 0x00, 0x00, 0xFF, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x08])
|
||||
h.grease(4)
|
||||
h.string(
|
||||
new Uint8Array([
|
||||
[
|
||||
0x00, 0x1D, 0x00, 0x17, 0x00, 0x18, 0x00, 0x0B, 0x00, 0x02, 0x01, 0x00,
|
||||
0x00, 0x23, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x0C, 0x02, 0x68,
|
||||
0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2F, 0x31, 0x2E, 0x31, 0x00, 0x05,
|
||||
|
@ -80,16 +80,16 @@ function executeTlsOperations(h: TlsHelloWriter): void {
|
|||
0x10, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05,
|
||||
0x01, 0x08, 0x06, 0x06, 0x01, 0x00, 0x12, 0x00, 0x00, 0x00, 0x33, 0x00,
|
||||
0x2B, 0x00, 0x29,
|
||||
]),
|
||||
],
|
||||
)
|
||||
h.grease(4)
|
||||
h.string(new Uint8Array([0x00, 0x01, 0x00, 0x00, 0x1D, 0x00, 0x20]))
|
||||
h.string([0x00, 0x01, 0x00, 0x00, 0x1D, 0x00, 0x20])
|
||||
h.key()
|
||||
h.string(new Uint8Array([0x00, 0x2D, 0x00, 0x02, 0x01, 0x01, 0x00, 0x2B, 0x00, 0x0B, 0x0A]))
|
||||
h.string([0x00, 0x2D, 0x00, 0x02, 0x01, 0x01, 0x00, 0x2B, 0x00, 0x0B, 0x0A])
|
||||
h.grease(6)
|
||||
h.string(new Uint8Array([0x03, 0x04, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x00, 0x1B, 0x00, 0x03, 0x02, 0x00, 0x02]))
|
||||
h.string([0x03, 0x04, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x00, 0x1B, 0x00, 0x03, 0x02, 0x00, 0x02])
|
||||
h.grease(3)
|
||||
h.string(new Uint8Array([0x00, 0x01, 0x00, 0x00, 0x15]))
|
||||
h.string([0x00, 0x01, 0x00, 0x00, 0x15])
|
||||
/* eslint-enable */
|
||||
}
|
||||
|
||||
|
@ -124,12 +124,12 @@ class TlsHelloWriter {
|
|||
domain: Uint8Array,
|
||||
) {
|
||||
this._domain = domain
|
||||
this.buf = new Uint8Array(size)
|
||||
this.buf = u8.alloc(size)
|
||||
this.dv = typed.toDataView(this.buf)
|
||||
this._grease = initGrease(this.crypto, 7)
|
||||
}
|
||||
|
||||
string(buf: Uint8Array) {
|
||||
string(buf: ArrayLike<number>) {
|
||||
this.buf.set(buf, this.pos)
|
||||
this.pos += buf.length
|
||||
}
|
||||
|
|
|
@ -167,7 +167,7 @@ export abstract class BaseMtProxyTransport implements TelegramTransport {
|
|||
u8.concat([
|
||||
helloRand,
|
||||
respBuf.slice(0, 11),
|
||||
new Uint8Array(32),
|
||||
u8.alloc(32),
|
||||
respBuf.slice(11 + 32),
|
||||
]),
|
||||
this._rawSecret,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { mtp, tl } from '@mtcute/tl'
|
||||
import { Deferred, Emitter } from '@fuman/utils'
|
||||
import { Deferred, Emitter, unknownToError } from '@fuman/utils'
|
||||
|
||||
import type { Logger } from '../utils/index.js'
|
||||
|
||||
|
@ -151,7 +151,7 @@ export class MultiSessionConnection {
|
|||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
this.onError.emit(err)
|
||||
this.onError.emit(unknownToError(err))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -342,8 +342,8 @@ export class MultiSessionConnection {
|
|||
}
|
||||
}
|
||||
|
||||
changeTransport(factory: TelegramTransport): void {
|
||||
this._connections.forEach(conn => conn.changeTransport(factory))
|
||||
async changeTransport(factory: TelegramTransport): Promise<void> {
|
||||
await Promise.all(this._connections.map(conn => conn.changeTransport(factory)))
|
||||
}
|
||||
|
||||
getPoolSize(): number {
|
||||
|
|
|
@ -2,12 +2,11 @@ import type { mtp, tl } from '@mtcute/tl'
|
|||
import type { TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime'
|
||||
import type Long from 'long'
|
||||
import { type ReconnectionStrategy, defaultReconnectionStrategy } from '@fuman/net'
|
||||
import { Deferred } from '@fuman/utils'
|
||||
import type { AsyncResourceContext, ComposedMiddleware, Middleware } from '@fuman/utils'
|
||||
import { Deferred, composeMiddlewares } from '@fuman/utils'
|
||||
|
||||
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'
|
||||
|
@ -279,7 +278,7 @@ export class DcConnectionManager {
|
|||
mainCount = this._mainCountOverride
|
||||
|
||||
if (mainCount === 0) {
|
||||
mainCount = this.manager.config.getNow()?.tmpSessions ?? 1
|
||||
mainCount = this.manager.config.getCached()?.tmpSessions ?? 1
|
||||
}
|
||||
} else {
|
||||
mainCount = 1
|
||||
|
@ -519,7 +518,7 @@ export class NetworkManager {
|
|||
this.call = this._composeCall(params.middlewares)
|
||||
|
||||
this._onConfigChanged = this._onConfigChanged.bind(this)
|
||||
config.onReload(this._onConfigChanged)
|
||||
config.onUpdated.add(this._onConfigChanged)
|
||||
|
||||
this._log = params.log.create('network')
|
||||
this._storage = params.storage
|
||||
|
@ -749,9 +748,9 @@ export class NetworkManager {
|
|||
dc.downloadSmall.resetSessions()
|
||||
}
|
||||
|
||||
private _onConfigChanged(config: tl.RawConfig): void {
|
||||
if (config.tmpSessions) {
|
||||
this._primaryDc?.setMainConnectionCount(config.tmpSessions)
|
||||
private _onConfigChanged({ current }: AsyncResourceContext<tl.RawConfig>): void {
|
||||
if (current?.tmpSessions) {
|
||||
this._primaryDc?.setMainConnectionCount(current.tmpSessions)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -866,12 +865,12 @@ export class NetworkManager {
|
|||
return res
|
||||
}
|
||||
|
||||
changeTransport(transport: TelegramTransport): void {
|
||||
async changeTransport(transport: TelegramTransport): Promise<void> {
|
||||
for (const dc of this._dcConnections.values()) {
|
||||
dc.main.changeTransport(transport)
|
||||
dc.upload.changeTransport(transport)
|
||||
dc.download.changeTransport(transport)
|
||||
dc.downloadSmall.changeTransport(transport)
|
||||
await dc.main.changeTransport(transport)
|
||||
await dc.upload.changeTransport(transport)
|
||||
await dc.download.changeTransport(transport)
|
||||
await dc.downloadSmall.changeTransport(transport)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -902,7 +901,7 @@ export class NetworkManager {
|
|||
await dc.destroy()
|
||||
}
|
||||
this._dcConnections.clear()
|
||||
this.config.offReload(this._onConfigChanged)
|
||||
this.config.onUpdated.remove(this._onConfigChanged)
|
||||
this._resetOnNetworkChange?.()
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { mtp } from '@mtcute/tl'
|
|||
import { tl } from '@mtcute/tl'
|
||||
import type { TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime'
|
||||
import { TlBinaryReader, TlBinaryWriter, TlSerializationCounter } from '@mtcute/tl-runtime'
|
||||
import { Deferred, Emitter, u8 } from '@fuman/utils'
|
||||
import { Deferred, Emitter, timers, u8 } from '@fuman/utils'
|
||||
|
||||
import { MtArgumentError, MtTimeoutError, MtcuteError } from '../types/index.js'
|
||||
import { createAesIgeForMessageOld } from '../utils/crypto/mtproto.js'
|
||||
|
@ -13,7 +13,6 @@ import {
|
|||
longFromBuffer,
|
||||
randomLong,
|
||||
removeFromLongArray,
|
||||
timers,
|
||||
} from '../utils/index.js'
|
||||
import type { ICorePlatform } from '../types/platform'
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ import { defaultTestCryptoProvider, useFakeMathRandom } from '@mtcute/test'
|
|||
import { hex } from '@fuman/utils'
|
||||
import { Bytes, write } from '@fuman/io'
|
||||
|
||||
import { IntermediatePacketCodec, PaddedIntermediatePacketCodec } from './intermediate'
|
||||
import { TransportError } from './abstract'
|
||||
import { IntermediatePacketCodec, PaddedIntermediatePacketCodec } from './intermediate.js'
|
||||
import { TransportError } from './abstract.js'
|
||||
|
||||
describe('IntermediatePacketCodec', () => {
|
||||
it('should return correct tag', () => {
|
||||
|
|
|
@ -179,8 +179,8 @@ describe('ObfuscatedPacketCodec', () => {
|
|||
|
||||
await codec.tag()
|
||||
|
||||
expect(codec.decode(Bytes.from(hex.decode(msg1)), false)).rejects.toThrow(TransportError)
|
||||
expect(codec.decode(Bytes.from(hex.decode(msg2)), false)).rejects.toThrow(TransportError)
|
||||
await expect(codec.decode(Bytes.from(hex.decode(msg1)), false)).rejects.toThrow(TransportError)
|
||||
await expect(codec.decode(Bytes.from(hex.decode(msg2)), false)).rejects.toThrow(TransportError)
|
||||
})
|
||||
|
||||
it('should correctly reset', async () => {
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
export type MaybePromise<T> = T | Promise<T>
|
||||
export type { MaybeArray, MaybePromise } from '@fuman/utils'
|
||||
|
||||
export type PartialExcept<T, K extends keyof T> = Partial<Omit<T, K>> & Pick<T, K>
|
||||
export type PartialOnly<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>
|
||||
export type AnyToNever<T> = any extends T ? never : T
|
||||
|
||||
export type MaybeArray<T> = T | T[]
|
||||
|
||||
export type MustEqual<T, V> = (() => T) extends () => V ? ((() => V) extends () => T ? T : V) : V
|
||||
|
||||
export type PublicPart<T> = { [K in keyof T]: T[K] }
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { u8 } from '@fuman/utils'
|
||||
|
||||
import type { MaybePromise } from '../../types/index.js'
|
||||
|
||||
import { factorizePQSync } from './factorization.js'
|
||||
|
@ -50,7 +52,7 @@ export abstract class BaseCryptoProvider {
|
|||
}
|
||||
|
||||
randomBytes(size: number): Uint8Array {
|
||||
const buf = new Uint8Array(size)
|
||||
const buf = u8.alloc(size)
|
||||
this.randomFill(buf)
|
||||
|
||||
return buf
|
||||
|
|
|
@ -42,7 +42,7 @@ export async function computeNewPasswordHash(
|
|||
): Promise<Uint8Array> {
|
||||
assertTypeIs('account.getPassword', algo, 'passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow')
|
||||
|
||||
const salt1 = new Uint8Array(algo.salt1.length + 32)
|
||||
const salt1 = u8.alloc(algo.salt1.length + 32)
|
||||
salt1.set(algo.salt1)
|
||||
crypto.randomFill(salt1.subarray(algo.salt1.length))
|
||||
;(algo as tl.Mutable<typeof algo>).salt1 = salt1
|
||||
|
|
|
@ -6,7 +6,6 @@ import { timers } from '@fuman/utils'
|
|||
*/
|
||||
export class EarlyTimer {
|
||||
private _timeout?: timers.Timer
|
||||
private _immediate?: timers.Immediate
|
||||
private _timeoutTs?: number
|
||||
|
||||
private _handler: () => void = () => {}
|
||||
|
@ -20,13 +19,11 @@ export class EarlyTimer {
|
|||
* (basically `setImmediate()`)
|
||||
*/
|
||||
emitWhenIdle(): void {
|
||||
if (this._immediate) return
|
||||
|
||||
timers.clearTimeout(this._timeout)
|
||||
this._timeoutTs = Date.now()
|
||||
|
||||
if (typeof timers.setImmediate !== 'undefined') {
|
||||
this._immediate = timers.setImmediate(this.emitNow)
|
||||
if (typeof queueMicrotask !== 'undefined') {
|
||||
queueMicrotask(this.emitNow)
|
||||
} else {
|
||||
this._timeout = timers.setTimeout(this.emitNow, 0)
|
||||
}
|
||||
|
@ -68,12 +65,7 @@ export class EarlyTimer {
|
|||
* Cancel the timer
|
||||
*/
|
||||
reset(): void {
|
||||
if (this._immediate) {
|
||||
timers.clearImmediate(this._immediate)
|
||||
this._immediate = undefined
|
||||
} else {
|
||||
timers.clearTimeout(this._timeout)
|
||||
}
|
||||
timers.clearTimeout(this._timeout)
|
||||
this._timeoutTs = undefined
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import * as timers from './timers.js'
|
||||
|
||||
export * from '../highlevel/utils/index.js'
|
||||
// todo: remove after 1.0.0
|
||||
export * from '../highlevel/storage/service/current-user.js'
|
||||
|
@ -8,7 +6,6 @@ export * from '../storage/service/base.js'
|
|||
export * from '../storage/service/default-dcs.js'
|
||||
// end todo
|
||||
export * from './bigint-utils.js'
|
||||
export * from './composer.js'
|
||||
export * from './crypto/index.js'
|
||||
export * from './dcs.js'
|
||||
export * from './early-timer.js'
|
||||
|
@ -23,4 +20,3 @@ export * from './sorted-array.js'
|
|||
export * from './tl-json.js'
|
||||
export * from './type-assertions.js'
|
||||
export * from '@mtcute/tl-runtime'
|
||||
export { timers }
|
||||
|
|
|
@ -1,61 +1 @@
|
|||
/* eslint-disable no-restricted-globals, ts/no-implied-eval */
|
||||
|
||||
// timers typings are mixed up across different runtimes, which leads
|
||||
// to the globals being typed incorrectly.
|
||||
// instead, we can treat the timers as opaque objects, and expose
|
||||
// them through the `timers` esm namespace.
|
||||
// this has near-zero runtime cost, but makes everything type-safe
|
||||
//
|
||||
// NB: we are using wrapper functions instead of...
|
||||
// - directly exposing the globals because the standard doesn't allow that
|
||||
// - .bind()-ing because it makes it harder to mock the timer globals
|
||||
|
||||
export interface Timer { readonly __type: 'Timer' }
|
||||
export interface Interval { readonly __type: 'Interval' }
|
||||
export interface Immediate { readonly __type: 'Immediate' }
|
||||
|
||||
const setTimeoutWrap = (
|
||||
(...args: Parameters<typeof setTimeout>) => setTimeout(...args)
|
||||
) as unknown as <T extends (...args: any[]) => any>(
|
||||
fn: T, ms: number, ...args: Parameters<T>
|
||||
) => Timer
|
||||
const setIntervalWrap = (
|
||||
(...args: Parameters<typeof setInterval>) => setInterval(...args)
|
||||
) as unknown as <T extends (...args: any[]) => any>(
|
||||
fn: T, ms: number, ...args: Parameters<T>
|
||||
) => Interval
|
||||
|
||||
let setImmediateWrap: any
|
||||
if (typeof setImmediate !== 'undefined') {
|
||||
setImmediateWrap = (...args: Parameters<typeof setImmediate>) => setImmediate(...args)
|
||||
} else {
|
||||
// eslint-disable-next-line
|
||||
setImmediateWrap = (fn: (...args: any[]) => void, ...args: any[]) => setTimeout(fn, 0, ...args)
|
||||
}
|
||||
const setImmediateWrapExported = setImmediateWrap as <T extends (...args: any[]) => any>(
|
||||
fn: T, ...args: Parameters<T>
|
||||
) => Immediate
|
||||
|
||||
const clearTimeoutWrap = (
|
||||
(...args: Parameters<typeof clearTimeout>) => clearTimeout(...args)
|
||||
) as unknown as (timer?: Timer) => void
|
||||
const clearIntervalWrap = (
|
||||
(...args: Parameters<typeof clearInterval>) => clearInterval(...args)
|
||||
) as unknown as (timer?: Interval) => void
|
||||
|
||||
let clearImmediateWrap: any
|
||||
if (typeof clearImmediate !== 'undefined') {
|
||||
clearImmediateWrap = (...args: Parameters<typeof clearImmediate>) => clearImmediate(...args)
|
||||
} else {
|
||||
clearImmediateWrap = (timer: number) => clearTimeout(timer)
|
||||
}
|
||||
const clearImmediateWrapExported = clearImmediateWrap as (timer?: Immediate) => void
|
||||
|
||||
export {
|
||||
setTimeoutWrap as setTimeout,
|
||||
setIntervalWrap as setInterval,
|
||||
setImmediateWrapExported as setImmediate,
|
||||
clearTimeoutWrap as clearTimeout,
|
||||
clearIntervalWrap as clearInterval,
|
||||
clearImmediateWrapExported as clearImmediate,
|
||||
}
|
||||
export { timers } from '@fuman/utils'
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
import { resolve } from 'node:path'
|
||||
import { cpSync } from 'node:fs'
|
||||
|
||||
/** @type {import('@fuman/build/vite').CustomBuildConfig} */
|
||||
export default () => ({
|
||||
buildCjs: false,
|
||||
final({ outDir, packageDir }) {
|
||||
viteConfig: {
|
||||
build: {
|
||||
lib: {
|
||||
formats: ['es'],
|
||||
},
|
||||
},
|
||||
},
|
||||
finalize({ outDir, packageDir }) {
|
||||
cpSync(resolve(packageDir, 'template'), resolve(outDir, 'template'), { recursive: true })
|
||||
},
|
||||
})
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
"create-bot": "./src/main.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "pnpm run -w build-package create-bot",
|
||||
"run": "tsx src/main.ts",
|
||||
"run:deno": "node scripts/generate-import-map.js && deno run --import-map=./scripts/import-map.json -A --unstable-sloppy-imports src/main.ts"
|
||||
},
|
||||
|
@ -26,5 +25,8 @@
|
|||
"@types/cross-spawn": "^6.0.6",
|
||||
"@types/inquirer": "^9.0.6",
|
||||
"@types/openurl": "^1.0.3"
|
||||
},
|
||||
"fuman": {
|
||||
"jsr": "skip"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable import/no-relative-packages, no-console, no-restricted-globals */
|
||||
/* eslint-disable no-console, no-restricted-globals */
|
||||
import { createHash } from 'node:crypto'
|
||||
import path from 'node:path'
|
||||
import * as fs from 'node:fs'
|
||||
|
@ -155,8 +155,9 @@ async function extractArtifacts(artifacts) {
|
|||
)
|
||||
}
|
||||
|
||||
/** @type {import('@fuman/build/vite').CustomBuildConfig} */
|
||||
export default () => ({
|
||||
async final({ packageDir, outDir }) {
|
||||
async finalize({ packageDir, outDir }) {
|
||||
const libDir = path.resolve(packageDir, 'lib')
|
||||
|
||||
if (!SKIP_PREBUILT) {
|
||||
|
|
|
@ -12,19 +12,19 @@
|
|||
"./native.js": "./src/native.cjs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "pnpm run -w build-package crypto-node",
|
||||
"install": "node-gyp-build",
|
||||
"rebuild": "node-gyp configure && node-gyp -j 16 rebuild",
|
||||
"clean": "node-gyp clean"
|
||||
},
|
||||
"keepScripts": [
|
||||
"install"
|
||||
],
|
||||
"dependencies": {
|
||||
"@mtcute/node": "workspace:^",
|
||||
"node-gyp-build": "4.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mtcute/test": "workspace:^"
|
||||
},
|
||||
"fuman": {
|
||||
"jsr": "skip",
|
||||
"keepScripts": ["install"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import * as fs from 'node:fs'
|
||||
|
||||
/** @type {import('@fuman/build/vite').CustomBuildConfig} */
|
||||
export default () => ({
|
||||
finalJsr({ outDir }) {
|
||||
finalizeJsr({ outDir }) {
|
||||
// jsr doesn't support symlinks, so we need to copy the files manually
|
||||
const real = fs.realpathSync(`${outDir}/common-internals-web`)
|
||||
fs.unlinkSync(`${outDir}/common-internals-web`)
|
||||
|
|
|
@ -12,16 +12,11 @@
|
|||
"./utils.js": "./src/utils.ts",
|
||||
"./methods.js": "./src/methods.ts"
|
||||
},
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"docs": "typedoc",
|
||||
"build": "pnpm run -w build-package deno"
|
||||
},
|
||||
"dependencies": {
|
||||
"@db/sqlite": "npm:@jsr/db__sqlite@0.12.0",
|
||||
"@fuman/deno": "workspace:^",
|
||||
"@fuman/net": "workspace:^",
|
||||
"@fuman/io": "workspace:^",
|
||||
"@fuman/utils": "0.0.1",
|
||||
"@fuman/net": "0.0.1",
|
||||
"@fuman/io": "0.0.1",
|
||||
"@mtcute/core": "workspace:^",
|
||||
"@mtcute/html-parser": "workspace:^",
|
||||
"@mtcute/markdown-parser": "workspace:^",
|
||||
|
@ -30,5 +25,8 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@mtcute/test": "workspace:^"
|
||||
},
|
||||
"fuman": {
|
||||
"jsr": "only"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
BaseTelegramClient as BaseTelegramClientBase,
|
||||
TelegramClient as TelegramClientBase,
|
||||
} from '@mtcute/core/client.js'
|
||||
import { unknownToError } from '@fuman/utils'
|
||||
|
||||
import { downloadToFile } from './methods/download-file.js'
|
||||
import { DenoPlatform } from './platform.js'
|
||||
|
@ -128,7 +129,7 @@ export class TelegramClient extends TelegramClientBase {
|
|||
|
||||
this.start(params)
|
||||
.then(then)
|
||||
.catch(err => this.emitError(err))
|
||||
.catch(err => this.onError.emit(unknownToError(err)))
|
||||
}
|
||||
|
||||
downloadToFile(
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { HttpProxySettings as FumanHttpProxySettings, ITcpConnection, SocksProxySettings, TcpEndpoint } from '@fuman/net'
|
||||
import { performHttpProxyHandshake, performSocksHandshake } from '@fuman/net'
|
||||
// @ts-expect-error wip
|
||||
import { connectTcp, connectTls } from '@fuman/deno'
|
||||
import { BaseMtProxyTransport, type ITelegramConnection, IntermediatePacketCodec, type TelegramTransport } from '@mtcute/core'
|
||||
import type { BasicDcOption } from '@mtcute/core/utils.js'
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// @ts-expect-error wip
|
||||
import { connectTcp } from '@fuman/deno'
|
||||
import type { ITcpConnection } from '@fuman/net'
|
||||
import { IntermediatePacketCodec, type TelegramTransport } from '@mtcute/core'
|
||||
|
|
|
@ -9,12 +9,11 @@
|
|||
"sideEffects": false,
|
||||
"exports": "./src/index.ts",
|
||||
"scripts": {
|
||||
"build": "pnpm run -w build-package dispatcher",
|
||||
"gen-updates": "node ./scripts/generate.cjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mtcute/core": "workspace:^",
|
||||
"@fuman/utils": "workspace:^",
|
||||
"@fuman/utils": "0.0.1",
|
||||
"events": "3.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -29,6 +29,7 @@ import {
|
|||
MtArgumentError,
|
||||
} from '@mtcute/core'
|
||||
import type { TelegramClient } from '@mtcute/core/client.js'
|
||||
import { unknownToError } from '@fuman/utils'
|
||||
|
||||
import type { UpdateContext } from './context/base.js'
|
||||
import type { BusinessMessageContext } from './context/business-message.js'
|
||||
|
@ -328,7 +329,8 @@ export class Dispatcher<State extends object = never> {
|
|||
|
||||
// order does not matter in the dispatcher,
|
||||
// so we can handle each update in its own task
|
||||
this.dispatchRawUpdateNow(update, peers).catch(err => this._client!.emitError(err))
|
||||
this.dispatchRawUpdateNow(update, peers)
|
||||
.catch(err => this._client!.onError.emit(unknownToError(err)))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -400,7 +402,8 @@ export class Dispatcher<State extends object = never> {
|
|||
|
||||
// order does not matter in the dispatcher,
|
||||
// so we can handle each update in its own task
|
||||
this.dispatchUpdateNow(update).catch(err => this._client!.emitError(err))
|
||||
this.dispatchUpdateNow(update)
|
||||
.catch(err => this._client!.onError.emit(unknownToError(err)))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { asyncResettable, timers } from '@mtcute/core/utils.js'
|
||||
import { asyncResettable } from '@mtcute/core/utils.js'
|
||||
import type { MaybePromise } from '@mtcute/core'
|
||||
import { LruMap } from '@fuman/utils'
|
||||
import { LruMap, timers } from '@fuman/utils'
|
||||
|
||||
import type { IStateStorageProvider } from './provider.js'
|
||||
|
||||
|
|
|
@ -10,12 +10,9 @@
|
|||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "pnpm run -w build-package file-id"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mtcute/tl-runtime": "workspace:^",
|
||||
"@fuman/utils": "workspace:^",
|
||||
"@fuman/utils": "0.0.1",
|
||||
"long": "5.2.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { TlBinaryWriter } from '@mtcute/tl-runtime'
|
||||
import { base64, utf8 } from '@fuman/utils'
|
||||
import { base64, u8, utf8 } from '@fuman/utils'
|
||||
|
||||
import { tdFileId as td } from './types.js'
|
||||
import { assertNever, telegramRleEncode } from './utils.js'
|
||||
|
@ -105,7 +105,7 @@ export function toFileId(location: Omit<td.RawFullRemoteFileLocation, '_'>): str
|
|||
}
|
||||
|
||||
const result = telegramRleEncode(writer.result())
|
||||
const withSuffix = new Uint8Array(result.length + SUFFIX.length)
|
||||
const withSuffix = u8.alloc(result.length + SUFFIX.length)
|
||||
withSuffix.set(result)
|
||||
withSuffix.set(SUFFIX, result.length)
|
||||
|
||||
|
|
|
@ -8,9 +8,6 @@
|
|||
"license": "MIT",
|
||||
"sideEffects": false,
|
||||
"exports": "./src/index.ts",
|
||||
"scripts": {
|
||||
"build": "pnpm run -w build-package html-parser"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mtcute/core": "workspace:^",
|
||||
"htmlparser2": "^6.0.1",
|
||||
|
|
|
@ -8,17 +8,8 @@
|
|||
"license": "MIT",
|
||||
"sideEffects": false,
|
||||
"exports": "./src/index.ts",
|
||||
"scripts": {
|
||||
"build": "pnpm run -w build-package i18n"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mtcute/core": "workspace:^",
|
||||
"@mtcute/dispatcher": "workspace:^"
|
||||
},
|
||||
"jsrOnlyFields": {
|
||||
"dependencies": {
|
||||
"@mtcute/core": "workspace:^",
|
||||
"@mtcute/dispatcher": "workspace:^"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,6 @@
|
|||
"license": "MIT",
|
||||
"sideEffects": false,
|
||||
"exports": "./src/index.ts",
|
||||
"scripts": {
|
||||
"build": "pnpm run -w build-package markdown-parser"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mtcute/core": "workspace:^",
|
||||
"long": "5.2.3"
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
/** @type {import('@fuman/build/vite').CustomBuildConfig} */
|
||||
export default () => {
|
||||
const clientId = fileURLToPath(new URL('./src/client.ts', import.meta.url))
|
||||
// const buildingCjs = false
|
||||
|
||||
return {
|
||||
external: ['@mtcute/crypto-node'],
|
||||
rollupPluginsPre: [
|
||||
viteConfig: {
|
||||
build: {
|
||||
rollupOptions: {
|
||||
external: ['@mtcute/crypto-node'],
|
||||
},
|
||||
},
|
||||
},
|
||||
pluginsPre: [
|
||||
{
|
||||
// very much a crutch, but it works
|
||||
// i couldn't figure out a way to hook into the esm->cjs transform,
|
||||
|
|
|
@ -12,21 +12,21 @@
|
|||
"./utils.js": "./src/utils.ts",
|
||||
"./methods.js": "./src/methods.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"docs": "typedoc",
|
||||
"build": "pnpm run -w build-package node"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mtcute/core": "workspace:^",
|
||||
"@mtcute/html-parser": "workspace:^",
|
||||
"@mtcute/markdown-parser": "workspace:^",
|
||||
"@mtcute/wasm": "workspace:^",
|
||||
"@fuman/net": "workspace:^",
|
||||
"@fuman/node": "workspace:^",
|
||||
"@fuman/utils": "0.0.1",
|
||||
"@fuman/net": "0.0.1",
|
||||
"@fuman/node": "0.0.1",
|
||||
"better-sqlite3": "11.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mtcute/test": "workspace:^",
|
||||
"@types/better-sqlite3": "7.6.4"
|
||||
},
|
||||
"fuman": {
|
||||
"jsr": "skip"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
BaseTelegramClient as BaseTelegramClientBase,
|
||||
TelegramClient as TelegramClientBase,
|
||||
} from '@mtcute/core/client.js'
|
||||
import { unknownToError } from '@fuman/utils'
|
||||
|
||||
import { downloadToFile } from './methods/download-file.js'
|
||||
import { downloadAsNodeStream } from './methods/download-node-stream.js'
|
||||
|
@ -139,7 +140,7 @@ export class TelegramClient extends TelegramClientBase {
|
|||
|
||||
this.start(params)
|
||||
.then(then)
|
||||
.catch(err => this.emitError(err))
|
||||
.catch(err => this.onError.emit(unknownToError(err)))
|
||||
}
|
||||
|
||||
downloadToFile(
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { Readable } from 'node:stream'
|
|||
|
||||
import type { FileDownloadLocation, FileDownloadParameters, ITelegramClient } from '@mtcute/core'
|
||||
import { downloadAsStream } from '@mtcute/core/methods.js'
|
||||
import { webStreamToNode } from '@fuman/node'
|
||||
import { webReadableToNode } from '@fuman/node'
|
||||
|
||||
/**
|
||||
* Download a remote file as a Node.js Readable stream.
|
||||
|
@ -14,5 +14,5 @@ export function downloadAsNodeStream(
|
|||
location: FileDownloadLocation,
|
||||
params?: FileDownloadParameters,
|
||||
): Readable {
|
||||
return webStreamToNode(downloadAsStream(client, location, params))
|
||||
return webReadableToNode(downloadAsStream(client, location, params))
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { basename } from 'node:path'
|
|||
import { Readable } from 'node:stream'
|
||||
|
||||
import type { UploadFileLike } from '@mtcute/core'
|
||||
import { nodeStreamToWeb } from '@fuman/node'
|
||||
import { nodeReadableToFuman } from '@fuman/node'
|
||||
|
||||
export async function normalizeFile(file: UploadFileLike): Promise<{
|
||||
file: UploadFileLike
|
||||
|
@ -20,7 +20,7 @@ export async function normalizeFile(file: UploadFileLike): Promise<{
|
|||
const fileSize = await stat(file.path.toString()).then(stat => stat.size)
|
||||
|
||||
return {
|
||||
file: nodeStreamToWeb(file),
|
||||
file: nodeReadableToFuman(file),
|
||||
fileName,
|
||||
fileSize,
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ export async function normalizeFile(file: UploadFileLike): Promise<{
|
|||
|
||||
if (file instanceof Readable) {
|
||||
return {
|
||||
file: nodeStreamToWeb(file),
|
||||
file: nodeReadableToFuman(file),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1,10 @@
|
|||
export default () => ({ buildCjs: false })
|
||||
/** @type {import('@fuman/build/vite').CustomBuildConfig} */
|
||||
export default () => ({
|
||||
viteConfig: {
|
||||
build: {
|
||||
lib: {
|
||||
formats: ['es'],
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -8,9 +8,6 @@
|
|||
"license": "MIT",
|
||||
"sideEffects": false,
|
||||
"exports": "./src/index.ts",
|
||||
"scripts": {
|
||||
"build": "pnpm run -w build-package test"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mtcute/core": "workspace:^",
|
||||
"@mtcute/node": "workspace:^",
|
||||
|
@ -28,13 +25,16 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"long": "5.2.3",
|
||||
"@fuman/utils": "workspace:^",
|
||||
"@fuman/net": "workspace:^"
|
||||
"@fuman/utils": "0.0.1",
|
||||
"@fuman/net": "0.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mtcute/tl-utils": "workspace:^"
|
||||
},
|
||||
"browser": {
|
||||
"./src/platform.js": "./src/platform.web.js"
|
||||
},
|
||||
"fuman": {
|
||||
"jsr": "skip"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ export class StubTelegramClient extends BaseTelegramClient {
|
|||
onMessage: (data, dcId) => {
|
||||
if (!this._onRawMessage) {
|
||||
if (this._responders.size) {
|
||||
this.emitError(new Error('Unexpected outgoing message'))
|
||||
this.onError.emit(new Error('Unexpected outgoing message'))
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -288,9 +288,11 @@ export class StubTelegramClient extends BaseTelegramClient {
|
|||
|
||||
let error: unknown
|
||||
|
||||
this.onError((err) => {
|
||||
const handler = (err: Error) => {
|
||||
error = err
|
||||
})
|
||||
}
|
||||
|
||||
this.onError.add(handler)
|
||||
|
||||
try {
|
||||
await fn()
|
||||
|
@ -300,6 +302,8 @@ export class StubTelegramClient extends BaseTelegramClient {
|
|||
|
||||
await this.close()
|
||||
|
||||
this.onError.remove(handler)
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Long from 'long'
|
||||
import type { tl } from '@mtcute/tl'
|
||||
import type { TlArgument } from '@mtcute/tl-utils'
|
||||
import { u8 } from '@fuman/utils'
|
||||
|
||||
import { getEntriesMap } from './schema.js'
|
||||
|
||||
|
@ -21,13 +22,13 @@ function getDefaultFor(arg: TlArgument): unknown {
|
|||
case 'long':
|
||||
return Long.ZERO
|
||||
case 'int128':
|
||||
return new Uint8Array(16)
|
||||
return u8.alloc(16)
|
||||
case 'int256':
|
||||
return new Uint8Array(32)
|
||||
return u8.alloc(32)
|
||||
case 'string':
|
||||
return ''
|
||||
case 'bytes':
|
||||
return new Uint8Array(0)
|
||||
return u8.alloc(0)
|
||||
case 'Bool':
|
||||
case 'bool':
|
||||
return false
|
||||
|
|
|
@ -10,12 +10,8 @@
|
|||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"docs": "typedoc",
|
||||
"build": "pnpm run -w build-package tl-runtime"
|
||||
},
|
||||
"dependencies": {
|
||||
"long": "5.2.3",
|
||||
"@fuman/utils": "workspace:^"
|
||||
"@fuman/utils": "0.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,10 +11,6 @@
|
|||
".": "./src/index.ts",
|
||||
"./json.js": "./src/json/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"docs": "typedoc",
|
||||
"build": "pnpm run -w build-package tl-utils"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mtcute/tl-runtime": "workspace:^",
|
||||
"crc-32": "1.2.0"
|
||||
|
|
|
@ -5,7 +5,7 @@ import { generateWriterCodeForTlEntries } from './codegen/writer.js'
|
|||
import { parseTlToEntries } from './parse.js'
|
||||
|
||||
function evalForResult<T>(js: string): T {
|
||||
// eslint-disable-next-line ts/no-implied-eval, no-new-func
|
||||
// eslint-disable-next-line ts/no-implied-eval, no-new-func, ts/no-unsafe-call
|
||||
return new Function(js)() as T
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
"gen-code": "tsx scripts/gen-code.ts",
|
||||
"gen-rsa": "tsx scripts/gen-rsa-keys.ts",
|
||||
"fetch-and-gen": "pnpm run fetch-api && pnpm run gen-code",
|
||||
"build": "pnpm run -w build-package tl"
|
||||
"build": "tsx scripts/build-package.ts",
|
||||
"build:jsr": "JSR=1 tsx scripts/build-package.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"long": "5.2.3"
|
||||
|
@ -22,7 +23,7 @@
|
|||
"@mtcute/core": "workspace:^",
|
||||
"@mtcute/node": "workspace:^",
|
||||
"@mtcute/tl-utils": "workspace:^",
|
||||
"@fuman/utils": "workspace:^",
|
||||
"@fuman/utils": "0.0.1",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"csv-parse": "^5.5.0",
|
||||
|
@ -31,7 +32,7 @@
|
|||
"typedoc": {
|
||||
"entryPoint": "index.d.ts"
|
||||
},
|
||||
"jsrOnlyFields": {
|
||||
"exports": {}
|
||||
"fuman": {
|
||||
"ownVersioning": true
|
||||
}
|
||||
}
|
107
packages/tl/scripts/build-package.ts
Normal file
107
packages/tl/scripts/build-package.ts
Normal file
|
@ -0,0 +1,107 @@
|
|||
import * as fsp from 'node:fs/promises'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
import { parsePackageJsonFile, processPackageJson } from '@fuman/build'
|
||||
import { packageJsonToDeno } from '@fuman/build/jsr'
|
||||
|
||||
async function transformFile(file: string, transform: (content: string, file: string) => string) {
|
||||
const content = await fsp.readFile(file, 'utf8')
|
||||
const res = transform(content, file)
|
||||
if (res != null) await fsp.writeFile(file, res)
|
||||
}
|
||||
|
||||
// create package by copying all the needed files
|
||||
const packageDir = fileURLToPath(new URL('../', import.meta.url))
|
||||
const outDir = process.env.FUMAN_BUILD_OUT ?? fileURLToPath(new URL('../dist', import.meta.url))
|
||||
|
||||
await fsp.rm(outDir, { recursive: true, force: true })
|
||||
|
||||
const files = [
|
||||
'binary/reader.d.ts',
|
||||
'binary/reader.js',
|
||||
'binary/rsa-keys.d.ts',
|
||||
'binary/rsa-keys.js',
|
||||
'binary/writer.d.ts',
|
||||
'binary/writer.js',
|
||||
'index.d.ts',
|
||||
'index.js',
|
||||
'raw-errors.json',
|
||||
'mtp-schema.json',
|
||||
'api-schema.json',
|
||||
'app-config.json',
|
||||
'README.md',
|
||||
]
|
||||
|
||||
await fsp.mkdir(resolve(outDir, 'binary'), { recursive: true })
|
||||
|
||||
for (const f of files) {
|
||||
await fsp.copyFile(resolve(packageDir, f), resolve(outDir, f))
|
||||
}
|
||||
|
||||
await fsp.cp(new URL('../../../LICENSE', import.meta.url), resolve(outDir, 'LICENSE'), { recursive: true })
|
||||
|
||||
const { packageJson, packageJsonOrig } = processPackageJson({
|
||||
packageJson: await parsePackageJsonFile(resolve(packageDir, 'package.json')),
|
||||
workspaceVersions: {},
|
||||
rootPackageJson: await parsePackageJsonFile(resolve(packageDir, '../../package.json')),
|
||||
})
|
||||
|
||||
if (process.env.JSR) {
|
||||
// jsr doesn't support cjs, so we'll need to add some shims
|
||||
// todo: remove this god awfulness when tl esm rewrite
|
||||
await transformFile(resolve(outDir, 'index.js'), (content) => {
|
||||
return [
|
||||
'/// <reference types="./index.d.ts" />',
|
||||
'const exports = {};',
|
||||
content,
|
||||
'export const tl = exports.tl;',
|
||||
'export const mtp = exports.mtp;',
|
||||
].join('\n')
|
||||
})
|
||||
await transformFile(resolve(outDir, 'binary/reader.js'), (content) => {
|
||||
return [
|
||||
'/// <reference types="./reader.d.ts" />',
|
||||
'const exports = {};',
|
||||
content,
|
||||
'export const __tlReaderMap = exports.__tlReaderMap;',
|
||||
].join('\n')
|
||||
})
|
||||
await transformFile(resolve(outDir, 'binary/writer.js'), (content) => {
|
||||
return [
|
||||
'/// <reference types="./writer.d.ts" />',
|
||||
'const exports = {};',
|
||||
content,
|
||||
'export const __tlWriterMap = exports.__tlWriterMap;',
|
||||
].join('\n')
|
||||
})
|
||||
await transformFile(resolve(outDir, 'binary/rsa-keys.js'), (content) => {
|
||||
return [
|
||||
'/// <reference types="./rsa-keys.d.ts" />',
|
||||
'const exports = {};',
|
||||
content,
|
||||
'export const __publicKeyIndex = exports.__publicKeyIndex;',
|
||||
].join('\n')
|
||||
})
|
||||
|
||||
// patch deno.json to add some export maps
|
||||
const denoJson = packageJsonToDeno({
|
||||
packageJson,
|
||||
packageJsonOrig,
|
||||
workspaceVersions: {},
|
||||
buildDirName: 'dist',
|
||||
})
|
||||
denoJson.exports = {}
|
||||
|
||||
for (const f of files) {
|
||||
if (!f.match(/\.js(?:on)?$/)) continue
|
||||
if (f === 'index.js') {
|
||||
denoJson.exports['.'] = './index.js'
|
||||
} else {
|
||||
denoJson.exports[`./${f}`] = `./${f}`
|
||||
}
|
||||
}
|
||||
await fsp.writeFile(resolve(outDir, 'deno.json'), JSON.stringify(denoJson, null, 2))
|
||||
} else {
|
||||
await fsp.writeFile(resolve(outDir, 'package.json'), JSON.stringify(packageJson, null, 2))
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
import { resolve } from 'node:path'
|
||||
import * as fs from 'node:fs'
|
||||
|
||||
/** @type {import('@fuman/build/vite').CustomBuildConfig} */
|
||||
export default () => ({
|
||||
finalPackageJson(pkg) {
|
||||
pkg.exports['./mtcute.wasm'] = './mtcute.wasm'
|
||||
finalizePackageJson({ packageJson }) {
|
||||
packageJson.exports['./mtcute.wasm'] = './mtcute.wasm'
|
||||
},
|
||||
final({ packageDir, outDir }) {
|
||||
finalize({ packageDir, outDir }) {
|
||||
fs.cpSync(resolve(packageDir, 'src/mtcute.wasm'), resolve(outDir, 'mtcute.wasm'))
|
||||
},
|
||||
})
|
||||
|
|
|
@ -12,17 +12,12 @@
|
|||
"./mtcute.wasm": "./src/mtcute.wasm"
|
||||
},
|
||||
"scripts": {
|
||||
"docs": "typedoc",
|
||||
"build": "pnpm run -w build-package wasm",
|
||||
"build:wasm": "docker build --output=lib --target=binaries lib"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mtcute/core": "workspace:^",
|
||||
"@mtcute/node": "workspace:^",
|
||||
"@mtcute/web": "workspace:^",
|
||||
"@fuman/utils": "workspace:^"
|
||||
},
|
||||
"jsrOnlyFields": {
|
||||
"exports": "./src/index.ts"
|
||||
"@fuman/utils": "0.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,6 @@ export function initSync(module: SyncInitInput): void {
|
|||
module = new WebAssembly.Instance(module)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
wasm = (module as unknown as WebAssembly.Instance).exports as unknown as MtcuteWasmModule
|
||||
initCommon()
|
||||
}
|
||||
|
|
|
@ -12,14 +12,10 @@
|
|||
"./utils.js": "./src/utils.ts",
|
||||
"./methods.js": "./src/methods.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"docs": "typedoc",
|
||||
"build": "pnpm run -w build-package web"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mtcute/core": "workspace:^",
|
||||
"@mtcute/wasm": "workspace:^",
|
||||
"@fuman/net": "workspace:^",
|
||||
"@fuman/net": "0.0.1",
|
||||
"events": "3.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
3028
pnpm-lock.yaml
3028
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,3 @@
|
|||
packages:
|
||||
- packages/*
|
||||
- private/fuman/packages/*
|
||||
- '!e2e/*'
|
||||
|
|
|
@ -1,210 +0,0 @@
|
|||
import { fileURLToPath } from 'node:url'
|
||||
import * as fs from 'node:fs'
|
||||
import * as cp from 'node:child_process'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
import { populateFromUpstream } from '@fuman/jsr'
|
||||
import * as glob from 'glob'
|
||||
import ts from 'typescript'
|
||||
|
||||
import { processPackageJson } from '../.config/vite-utils/package-json.js'
|
||||
|
||||
export function packageJsonToDeno({ packageJson, packageJsonOrig }) {
|
||||
// https://jsr.io/docs/package-configuration
|
||||
|
||||
const importMap = {}
|
||||
const exports = {}
|
||||
|
||||
if (packageJson.dependencies) {
|
||||
for (const [name, version] of Object.entries(packageJson.dependencies)) {
|
||||
if (name.startsWith('@mtcute/')) {
|
||||
importMap[name] = `jsr:${name}@${version}`
|
||||
} else if (version.startsWith('npm:@jsr/')) {
|
||||
const jsrName = version.slice(9).split('@')[0].replace('__', '/')
|
||||
const jsrVersion = version.slice(9).split('@')[1]
|
||||
importMap[name] = `jsr:@${jsrName}@${jsrVersion}`
|
||||
} else {
|
||||
importMap[name] = `npm:${name}@${version}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (packageJsonOrig.exports) {
|
||||
let tmpExports
|
||||
if (typeof packageJsonOrig.exports === 'string') {
|
||||
tmpExports = { '.': packageJsonOrig.exports }
|
||||
} else if (typeof packageJsonOrig.exports !== 'object') {
|
||||
throw new TypeError('package.json exports must be an object')
|
||||
} else {
|
||||
tmpExports = packageJsonOrig.exports
|
||||
}
|
||||
|
||||
for (const [name, value] of Object.entries(tmpExports)) {
|
||||
if (typeof value !== 'string') {
|
||||
throw new TypeError(`package.json exports value must be a string: ${name}`)
|
||||
}
|
||||
if (value.endsWith('.wasm')) continue
|
||||
|
||||
exports[name] = value
|
||||
.replace(/^\.\/src\//, './')
|
||||
.replace(/\.js$/, '.ts')
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: packageJson.name,
|
||||
version: packageJson.version,
|
||||
exports,
|
||||
exclude: ['**/*.test.ts', '**/*.test-utils.ts', '**/__fixtures__/**'],
|
||||
imports: importMap,
|
||||
publish: {
|
||||
exclude: ['!../dist'], // lol
|
||||
},
|
||||
...packageJson.denoJson,
|
||||
}
|
||||
}
|
||||
|
||||
export async function runJsrBuildSync(packageName) {
|
||||
const packageDir = fileURLToPath(new URL(`../packages/${packageName}`, import.meta.url))
|
||||
const outDir = fileURLToPath(new URL(`../packages/${packageName}/dist/jsr`, import.meta.url))
|
||||
fs.rmSync(outDir, { recursive: true, force: true })
|
||||
fs.mkdirSync(outDir, { recursive: true })
|
||||
|
||||
console.log('[i] Copying sources...')
|
||||
fs.cpSync(resolve(packageDir, 'src'), outDir, { recursive: true })
|
||||
|
||||
const printer = ts.createPrinter()
|
||||
|
||||
for (const f of glob.sync(resolve(outDir, '**/*.ts'))) {
|
||||
let fileContent = fs.readFileSync(f, 'utf8')
|
||||
let changed = false
|
||||
|
||||
// replace .js imports with .ts
|
||||
const file = ts.createSourceFile(f, fileContent, ts.ScriptTarget.ESNext, true)
|
||||
let changedTs = false
|
||||
|
||||
for (const imp of file.statements) {
|
||||
if (imp.kind !== ts.SyntaxKind.ImportDeclaration && imp.kind !== ts.SyntaxKind.ExportDeclaration) {
|
||||
continue
|
||||
}
|
||||
if (imp.kind === ts.SyntaxKind.ExportDeclaration && !imp.moduleSpecifier) {
|
||||
continue
|
||||
}
|
||||
const mod = imp.moduleSpecifier.text
|
||||
|
||||
if (mod[0] === '.' && mod.endsWith('.js')) {
|
||||
changedTs = true
|
||||
imp.moduleSpecifier = {
|
||||
kind: ts.SyntaxKind.StringLiteral,
|
||||
text: `${mod.slice(0, -3)}.ts`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (changedTs) {
|
||||
fileContent = printer.printFile(file)
|
||||
changed = true
|
||||
}
|
||||
|
||||
// add shims for node-specific APIs and replace NodeJS.* types
|
||||
// pretty fragile, but it works for now
|
||||
const typesToReplace = {
|
||||
'NodeJS\\.Timeout': 'number',
|
||||
'NodeJS\\.Immediate': 'number',
|
||||
}
|
||||
const nodeSpecificApis = {
|
||||
setImmediate: '(cb: (...args: any[]) => void, ...args: any[]) => number',
|
||||
clearImmediate: '(id: number) => void',
|
||||
Buffer:
|
||||
'{ '
|
||||
+ 'concat: (...args: any[]) => Uint8Array, '
|
||||
+ 'from: (data: any, encoding?: string) => { toString(encoding?: string): string }, '
|
||||
+ ' }',
|
||||
SharedWorker: ['type', 'never'],
|
||||
WorkerGlobalScope:
|
||||
'{ '
|
||||
+ ' new (): typeof WorkerGlobalScope, '
|
||||
+ ' postMessage: (message: any, transfer?: Transferable[]) => void, '
|
||||
+ ' addEventListener: (type: "message", listener: (ev: MessageEvent) => void) => void, '
|
||||
+ ' }',
|
||||
process: '{ ' + 'hrtime: { bigint: () => bigint }, ' + '}',
|
||||
}
|
||||
|
||||
for (const [name, decl_] of Object.entries(nodeSpecificApis)) {
|
||||
if (fileContent.includes(name)) {
|
||||
if (name === 'Buffer' && fileContent.includes('node:buffer')) continue
|
||||
|
||||
changed = true
|
||||
const isType = Array.isArray(decl_) && decl_[0] === 'type'
|
||||
const decl = isType ? decl_[1] : decl_
|
||||
|
||||
if (isType) {
|
||||
fileContent = `declare type ${name} = ${decl};\n${fileContent}`
|
||||
} else {
|
||||
fileContent = `declare const ${name}: ${decl};\n${fileContent}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const [oldType, newType] of Object.entries(typesToReplace)) {
|
||||
if (fileContent.match(oldType)) {
|
||||
changed = true
|
||||
fileContent = fileContent.replace(new RegExp(oldType, 'g'), newType)
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
fs.writeFileSync(f, fileContent)
|
||||
}
|
||||
}
|
||||
|
||||
const { packageJson, packageJsonOrig } = processPackageJson(packageDir)
|
||||
const denoJson = packageJsonToDeno({ packageJson, packageJsonOrig })
|
||||
|
||||
fs.writeFileSync(resolve(outDir, 'deno.json'), JSON.stringify(denoJson, null, 2))
|
||||
fs.cpSync(new URL('../LICENSE', import.meta.url), resolve(outDir, 'LICENSE'), { recursive: true })
|
||||
|
||||
if (process.env.E2E) {
|
||||
// populate dependencies, if any
|
||||
const depsToPopulate = []
|
||||
|
||||
for (const dep of Object.values(denoJson.imports)) {
|
||||
if (!dep.startsWith('jsr:')) continue
|
||||
if (dep.startsWith('jsr:@mtcute/')) continue
|
||||
depsToPopulate.push(dep.slice(4))
|
||||
}
|
||||
|
||||
if (depsToPopulate.length) {
|
||||
console.log('[i] Populating %d dependencies...', depsToPopulate.length)
|
||||
await populateFromUpstream({
|
||||
downstream: process.env.JSR_URL,
|
||||
token: process.env.JSR_TOKEN,
|
||||
unstable_createViaApi: true,
|
||||
packages: depsToPopulate,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let customConfig
|
||||
try {
|
||||
customConfig = await (await import(resolve(packageDir, 'build.config.js'))).default()
|
||||
} catch {}
|
||||
|
||||
if (customConfig) {
|
||||
await customConfig.finalJsr?.({ packageDir, outDir })
|
||||
}
|
||||
|
||||
console.log('[i] Trying to publish with --dry-run')
|
||||
cp.execSync('deno publish --dry-run --allow-dirty --quiet', { cwd: outDir, stdio: 'inherit' })
|
||||
console.log('[v] All good!')
|
||||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
const PACKAGE_NAME = process.argv[2]
|
||||
|
||||
if (!PACKAGE_NAME) {
|
||||
throw new Error('package name not specified')
|
||||
}
|
||||
|
||||
await runJsrBuildSync(PACKAGE_NAME)
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
/* eslint-disable node/prefer-global/process */
|
||||
import * as cp from 'node:child_process'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const configPath = fileURLToPath(new URL('../.config/vite.build.ts', import.meta.url))
|
||||
|
||||
export function runViteBuildSync(packageName) {
|
||||
cp.execSync(`pnpm exec vite build --config "${configPath}"`, {
|
||||
stdio: 'inherit',
|
||||
cwd: fileURLToPath(new URL(`../packages/${packageName}`, import.meta.url)),
|
||||
})
|
||||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
const PACKAGE_NAME = process.argv[2]
|
||||
|
||||
if (!PACKAGE_NAME) {
|
||||
throw new Error('package name not specified')
|
||||
}
|
||||
|
||||
runViteBuildSync(PACKAGE_NAME)
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
import * as fs from 'node:fs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
import { processPackageJson } from '../.config/vite-utils/package-json.js'
|
||||
|
||||
import { packageJsonToDeno, runJsrBuildSync } from './build-package-jsr.js'
|
||||
import { runViteBuildSync } from './build-package-vite.js'
|
||||
|
||||
if (process.argv.length < 3) {
|
||||
console.log('Usage: build-package.js <package name>')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const IS_JSR = process.env.JSR === '1'
|
||||
|
||||
const packageName = process.argv[2]
|
||||
|
||||
function transformFile(file, transform) {
|
||||
const content = fs.readFileSync(file, 'utf8')
|
||||
const res = transform(content, file)
|
||||
if (res != null) fs.writeFileSync(file, res)
|
||||
}
|
||||
|
||||
if (packageName === 'tl') {
|
||||
// create package by copying all the needed files
|
||||
const packageDir = fileURLToPath(new URL('../packages/tl', import.meta.url))
|
||||
let outDir = fileURLToPath(new URL('../packages/tl/dist', import.meta.url))
|
||||
if (IS_JSR) outDir = resolve(outDir, 'jsr')
|
||||
|
||||
fs.rmSync(outDir, { recursive: true, force: true })
|
||||
|
||||
const files = [
|
||||
'binary/reader.d.ts',
|
||||
'binary/reader.js',
|
||||
'binary/rsa-keys.d.ts',
|
||||
'binary/rsa-keys.js',
|
||||
'binary/writer.d.ts',
|
||||
'binary/writer.js',
|
||||
'index.d.ts',
|
||||
'index.js',
|
||||
'raw-errors.json',
|
||||
'mtp-schema.json',
|
||||
'api-schema.json',
|
||||
'app-config.json',
|
||||
'README.md',
|
||||
]
|
||||
|
||||
fs.mkdirSync(resolve(outDir, 'binary'), { recursive: true })
|
||||
|
||||
for (const f of files) {
|
||||
fs.copyFileSync(resolve(packageDir, f), resolve(outDir, f))
|
||||
}
|
||||
|
||||
fs.cpSync(new URL('../LICENSE', import.meta.url), resolve(outDir, 'LICENSE'), { recursive: true })
|
||||
const { packageJson, packageJsonOrig } = processPackageJson(packageDir)
|
||||
|
||||
if (IS_JSR) {
|
||||
// jsr doesn't support cjs, so we'll need to add some shims
|
||||
// todo: remove this god awfulness when tl esm rewrite
|
||||
transformFile(resolve(outDir, 'index.js'), (content) => {
|
||||
return [
|
||||
'/// <reference types="./index.d.ts" />',
|
||||
'const exports = {};',
|
||||
content,
|
||||
'export const tl = exports.tl;',
|
||||
'export const mtp = exports.mtp;',
|
||||
].join('\n')
|
||||
})
|
||||
transformFile(resolve(outDir, 'binary/reader.js'), (content) => {
|
||||
return [
|
||||
'/// <reference types="./reader.d.ts" />',
|
||||
'const exports = {};',
|
||||
content,
|
||||
'export const __tlReaderMap = exports.__tlReaderMap;',
|
||||
].join('\n')
|
||||
})
|
||||
transformFile(resolve(outDir, 'binary/writer.js'), (content) => {
|
||||
return [
|
||||
'/// <reference types="./writer.d.ts" />',
|
||||
'const exports = {};',
|
||||
content,
|
||||
'export const __tlWriterMap = exports.__tlWriterMap;',
|
||||
].join('\n')
|
||||
})
|
||||
transformFile(resolve(outDir, 'binary/rsa-keys.js'), (content) => {
|
||||
return [
|
||||
'/// <reference types="./rsa-keys.d.ts" />',
|
||||
'const exports = {};',
|
||||
content,
|
||||
'export const __publicKeyIndex = exports.__publicKeyIndex;',
|
||||
].join('\n')
|
||||
})
|
||||
|
||||
// patch deno.json to add some export maps
|
||||
const denoJson = packageJsonToDeno({ packageJson, packageJsonOrig })
|
||||
denoJson.exports = {}
|
||||
|
||||
for (const f of files) {
|
||||
if (!f.match(/\.js(?:on)?$/)) continue
|
||||
if (f === 'index.js') {
|
||||
denoJson.exports['.'] = './index.js'
|
||||
} else {
|
||||
denoJson.exports[`./${f}`] = `./${f}`
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(resolve(outDir, 'deno.json'), JSON.stringify(denoJson, null, 2))
|
||||
} else {
|
||||
fs.writeFileSync(resolve(outDir, 'package.json'), JSON.stringify(packageJson, null, 2))
|
||||
}
|
||||
} else {
|
||||
if (IS_JSR) {
|
||||
await runJsrBuildSync(packageName)
|
||||
} else {
|
||||
runViteBuildSync(packageName)
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
import { appendFileSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
|
||||
import { EOL } from 'node:os'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { inc, rcompare } from 'semver'
|
||||
|
||||
const __dirname = dirname(new URL(import.meta.url).pathname)
|
||||
|
||||
function collectPackageJsons() {
|
||||
return readdirSync(join(__dirname, '../packages'))
|
||||
.filter(s => !s.startsWith('.'))
|
||||
.map((name) => {
|
||||
try {
|
||||
return JSON.parse(readFileSync(join(__dirname, '../packages', name, 'package.json'), 'utf-8'))
|
||||
} catch (e) {
|
||||
if (e.code !== 'ENOENT') throw e
|
||||
|
||||
return null
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
function bumpVersions(packages, kind) {
|
||||
const pkgJsons = collectPackageJsons()
|
||||
const maxVersion = pkgJsons
|
||||
.filter(it => it.name !== '@mtcute/tl')
|
||||
.map(it => it.version)
|
||||
.sort(rcompare)[0]
|
||||
|
||||
const nextVersion = inc(maxVersion, kind)
|
||||
console.log('[i] Bumping versions to %s', nextVersion)
|
||||
|
||||
for (const pkg of packages) {
|
||||
if (pkg === 'tl') continue // own versioning
|
||||
const pkgJson = pkgJsons.find(it => it.name === `@mtcute/${pkg}`)
|
||||
|
||||
if (!pkgJson) {
|
||||
console.error(`Package ${pkg} not found!`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
pkgJson.version = nextVersion
|
||||
writeFileSync(
|
||||
join(__dirname, '../packages', pkg, 'package.json'),
|
||||
`${JSON.stringify(pkgJson, null, 2)}\n`,
|
||||
)
|
||||
}
|
||||
|
||||
const rootPkgJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'))
|
||||
rootPkgJson.version = nextVersion
|
||||
writeFileSync(join(__dirname, '../package.json'), `${JSON.stringify(rootPkgJson, null, 2)}\n`)
|
||||
|
||||
return nextVersion
|
||||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
const kind = process.argv[2]
|
||||
const packages = process.argv[3]
|
||||
|
||||
if (!packages || !kind) {
|
||||
console.log('Usage: bump-version.js <major|minor|patch> <package1,package2>')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const packagesList = packages.split(',')
|
||||
|
||||
if (packagesList.length === 0) {
|
||||
console.error('No packages specified!')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (kind === 'major' && packagesList.length !== collectPackageJsons().length) {
|
||||
console.error('Cannot bump major version only for some packages!')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const ver = bumpVersions(packagesList, kind)
|
||||
|
||||
if (process.env.GITHUB_OUTPUT) {
|
||||
appendFileSync(process.env.GITHUB_OUTPUT, `version=${ver}${EOL}`)
|
||||
}
|
||||
}
|
||||
|
||||
export { bumpVersions }
|
|
@ -1,111 +0,0 @@
|
|||
import { execSync } from 'node:child_process'
|
||||
import { appendFileSync, existsSync } from 'node:fs'
|
||||
import { EOL } from 'node:os'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { findChangedFilesSince, getLatestTag } from './git-utils.js'
|
||||
import { listPackages } from './publish.js'
|
||||
|
||||
getTsconfigFiles.cache = {}
|
||||
|
||||
const __dirname = dirname(new URL(import.meta.url).pathname)
|
||||
|
||||
function getTsconfigFiles(pkg) {
|
||||
if (!existsSync(join(__dirname, `../packages/${pkg}/tsconfig.json`))) {
|
||||
throw new Error(`[!] ${pkg} does not have a tsconfig.json`)
|
||||
}
|
||||
if (pkg in getTsconfigFiles.cache) return getTsconfigFiles.cache[pkg]
|
||||
|
||||
console.log('[i] Getting tsconfig files for %s', pkg)
|
||||
const res = execSync('pnpm exec tsc --showConfig', {
|
||||
encoding: 'utf8',
|
||||
stdio: 'pipe',
|
||||
cwd: join(__dirname, `../packages/${pkg}`),
|
||||
})
|
||||
|
||||
const json = JSON.parse(res)
|
||||
|
||||
return (getTsconfigFiles.cache[pkg] = json.files.map(it => it.replace(/^\.\//, '')))
|
||||
}
|
||||
|
||||
function isMeaningfulChange(pkg, path) {
|
||||
// some magic heuristics stuff
|
||||
|
||||
if (path.match(/\.(md|test(?:-utils)?\.ts)$/i)) return false
|
||||
|
||||
if (getTsconfigFiles(pkg).includes(path)) {
|
||||
console.log('[i] %s: %s is in tsconfig', pkg, path)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if (path.match(/typedoc\.cjs$/i)) return false
|
||||
if (path.match(/^(scripts|dist|tests|private)\//i)) return false
|
||||
|
||||
console.log('[i] %s: %s is a meaningful change', pkg, path)
|
||||
|
||||
// to be safe
|
||||
return true
|
||||
}
|
||||
|
||||
function findChangedPackagesSince(tag, until) {
|
||||
const packages = new Set(listPackages(true))
|
||||
const changedFiles = findChangedFilesSince(tag, until)
|
||||
|
||||
const changedPackages = new Set()
|
||||
|
||||
for (const file of changedFiles) {
|
||||
const [dir, pkgname, ...rest] = file.split('/')
|
||||
if (dir !== 'packages') continue
|
||||
if (!packages.has(pkgname)) continue
|
||||
|
||||
// already checked, no need to check again
|
||||
if (changedPackages.has(pkgname)) continue
|
||||
|
||||
const relpath = rest.join('/')
|
||||
|
||||
if (isMeaningfulChange(pkgname, relpath)) {
|
||||
changedPackages.add(pkgname)
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(changedPackages)
|
||||
}
|
||||
|
||||
export { findChangedPackagesSince, getLatestTag }
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url) && process.env.CI && process.env.GITHUB_OUTPUT) {
|
||||
const kind = process.argv[2]
|
||||
const input = process.argv[3]
|
||||
|
||||
if (!input) {
|
||||
// for simpler flow, one can pass all or package list as the first argument,
|
||||
// and they will be returned as is, so that we can later simply
|
||||
// use the outputs of this script
|
||||
console.log('Usage: find-updated-packages.js <packages>')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (kind === 'major' && input !== 'all') {
|
||||
throw new Error('For major releases, all packages must be published')
|
||||
}
|
||||
|
||||
console.log('[i] Determining packages to publish...')
|
||||
|
||||
let res
|
||||
|
||||
if (input === 'all') {
|
||||
res = listPackages(true)
|
||||
} else if (input === 'updated') {
|
||||
const tag = getLatestTag()
|
||||
console.log('[i] Latest tag is %s', tag)
|
||||
|
||||
res = findChangedPackagesSince(tag)
|
||||
} else {
|
||||
res = input.split(',')
|
||||
}
|
||||
|
||||
console.log('[i] Will publish:', res)
|
||||
appendFileSync(process.env.GITHUB_OUTPUT, `modified=${res.join(',')}${EOL}`)
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
// node scripts/gen-deps-graph.js | dot -Tsvg > deps.svg
|
||||
import { getPackageJsons } from './utils.js'
|
||||
|
||||
const packageJsons = await getPackageJsons()
|
||||
|
||||
function getMtcuteName(name) {
|
||||
if (!name.startsWith('@mtcute/')) return null
|
||||
|
||||
return name.slice(8)
|
||||
}
|
||||
|
||||
const output = []
|
||||
|
||||
for (const pkg of packageJsons) {
|
||||
if (!pkg) continue
|
||||
|
||||
const name = getMtcuteName(pkg.name)
|
||||
|
||||
if (!name) continue
|
||||
|
||||
for (const dep of Object.keys(pkg.dependencies || {})) {
|
||||
const depName = getMtcuteName(dep)
|
||||
if (!depName) continue
|
||||
|
||||
output.push(`"${name}" -> "${depName}"`)
|
||||
}
|
||||
|
||||
for (const dep of Object.keys(pkg.devDependencies || {})) {
|
||||
const depName = getMtcuteName(dep)
|
||||
if (!depName) continue
|
||||
|
||||
output.push(`"${name}" -> "${depName}" [style=dashed,color=grey]`)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('digraph {')
|
||||
console.log(output.join('\n'))
|
||||
console.log('}')
|
|
@ -1,74 +0,0 @@
|
|||
import { randomUUID } from 'node:crypto'
|
||||
import { appendFileSync } from 'node:fs'
|
||||
import { EOL } from 'node:os'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { findChangedFilesSince, getCommitsSince, getLatestTag, parseConventionalCommit } from './git-utils.js'
|
||||
|
||||
function generateChangelog(onlyPackages) {
|
||||
const byPackage = {}
|
||||
|
||||
for (const commit of getCommitsSince(getLatestTag())) {
|
||||
const parsed = parseConventionalCommit(commit.msg)
|
||||
|
||||
if (!parsed) {
|
||||
console.warn('[warn] Failed to parse commit message: %s', commit.msg)
|
||||
continue
|
||||
}
|
||||
|
||||
const { type, breaking } = parsed
|
||||
|
||||
if ((!type || ['chore', 'ci', 'docs', 'test'].includes(type)) && !breaking) continue
|
||||
|
||||
const changed = findChangedFilesSince(`${commit.hash}~1`, commit.hash)
|
||||
|
||||
let line = `- ${commit.hash}: ${breaking ? '**❗ BREAKING** ' : ''}${commit.msg}`
|
||||
|
||||
if (breaking && commit.description) {
|
||||
line
|
||||
+= `\n${
|
||||
commit.description
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map(line => ` ${line}`)
|
||||
.join('\n')}`
|
||||
}
|
||||
|
||||
for (const file of changed) {
|
||||
if (!file.startsWith('packages/')) continue
|
||||
const pkg = file.split('/')[1]
|
||||
if (onlyPackages && !onlyPackages.includes(pkg)) continue
|
||||
|
||||
if (!byPackage[pkg]) byPackage[pkg] = {}
|
||||
byPackage[pkg][commit.hash] = line
|
||||
// console.log('including %s in %s because of %s', commit.hash, pkg, file)
|
||||
}
|
||||
}
|
||||
|
||||
let ret = ''
|
||||
|
||||
for (const [pkg, lines] of Object.entries(byPackage)) {
|
||||
ret += `### ${pkg}\n`
|
||||
ret += Object.values(lines).join('\n')
|
||||
ret += '\n\n'
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
let onlyPackages = null
|
||||
|
||||
if (process.argv[2]) {
|
||||
onlyPackages = process.argv[2].split(',')
|
||||
}
|
||||
|
||||
const res = generateChangelog(onlyPackages)
|
||||
|
||||
if (process.env.CI && process.env.GITHUB_OUTPUT) {
|
||||
const delim = `---${randomUUID()}---${EOL}`
|
||||
appendFileSync(process.env.GITHUB_OUTPUT, `changelog<<${delim}${res}${delim}`)
|
||||
} else {
|
||||
console.log(res)
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
import { execSync } from 'node:child_process'
|
||||
|
||||
function getLatestTag() {
|
||||
try {
|
||||
const res = execSync('git describe --abbrev=0 --tags', { encoding: 'utf8', stdio: 'pipe' }).trim()
|
||||
|
||||
return res
|
||||
} catch (e) {
|
||||
if (e.stderr.match(/^fatal: (No names found|No tags can describe)/i)) {
|
||||
// no tags found, let's just return the first commit
|
||||
return execSync('git rev-list --max-parents=0 HEAD', { encoding: 'utf8' }).trim()
|
||||
}
|
||||
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
function findChangedFilesSince(tag, until = 'HEAD') {
|
||||
return execSync(`git diff --name-only ${tag} ${until}`, { encoding: 'utf8', stdio: 'pipe' }).trim().split('\n')
|
||||
}
|
||||
|
||||
function getCommitsSince(tag, until = 'HEAD') {
|
||||
const delim = `---${Math.random().toString(36).slice(2)}---`
|
||||
|
||||
const lines = execSync(`git log --pretty="format:%H %s%n%b%n${delim}" ${tag}..${until}`, { encoding: 'utf8', stdio: 'pipe' })
|
||||
.trim()
|
||||
.split('\n')
|
||||
|
||||
const items = []
|
||||
|
||||
let current = null
|
||||
|
||||
for (const line of lines) {
|
||||
if (line === delim) {
|
||||
if (current) items.push(current)
|
||||
current = null
|
||||
} else if (current) {
|
||||
if (current.description) current.description += '\n'
|
||||
current.description += line
|
||||
} else {
|
||||
const [hash, ...msg] = line.split(' ')
|
||||
current = { hash, msg: msg.join(' '), description: '' }
|
||||
}
|
||||
}
|
||||
|
||||
if (current) items.push(current)
|
||||
|
||||
return items.reverse()
|
||||
}
|
||||
|
||||
function getCurrentCommit() {
|
||||
return execSync('git rev-parse HEAD', { encoding: 'utf8', stdio: 'pipe' }).trim()
|
||||
}
|
||||
|
||||
function getCurrentBranch() {
|
||||
return execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8', stdio: 'pipe' }).trim()
|
||||
}
|
||||
|
||||
function parseConventionalCommit(msg) {
|
||||
const match = msg.match(/^(\w+)(?:\(([^)]+)\))?(!?): (.+)$/)
|
||||
|
||||
if (!match) return null
|
||||
|
||||
const [, type, scope, breaking, subject] = match
|
||||
|
||||
return { type, scope, breaking: Boolean(breaking), subject }
|
||||
}
|
||||
|
||||
export {
|
||||
findChangedFilesSince,
|
||||
getCommitsSince,
|
||||
getCurrentBranch,
|
||||
getCurrentCommit,
|
||||
getLatestTag,
|
||||
parseConventionalCommit,
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue