chore!: started extracting platform-specific stuff into separate packages
This commit is contained in:
parent
ceb606a347
commit
a2739b678c
160 changed files with 1452 additions and 772 deletions
|
@ -254,7 +254,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/scripts/**', '*.test.ts', 'packages/create-*/**', '**/build.config.cjs'],
|
files: ['**/scripts/**', '*.test.ts', 'packages/create-*/**', '**/build.config.cjs', 'packages/node/**'],
|
||||||
rules: {
|
rules: {
|
||||||
'no-console': 'off',
|
'no-console': 'off',
|
||||||
'no-restricted-imports': [
|
'no-restricted-imports': [
|
||||||
|
@ -273,7 +273,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['e2e/**'],
|
files: ['e2e/**', 'packages/node/**'],
|
||||||
rules: {
|
rules: {
|
||||||
'no-restricted-globals': 'off',
|
'no-restricted-globals': 'off',
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,18 +12,12 @@
|
||||||
"gen-client": "node ./scripts/generate-client.cjs",
|
"gen-client": "node ./scripts/generate-client.cjs",
|
||||||
"gen-updates": "node ./scripts/generate-updates.cjs"
|
"gen-updates": "node ./scripts/generate-updates.cjs"
|
||||||
},
|
},
|
||||||
"browser": {
|
"exports": {
|
||||||
"./src/utils/platform/crypto.js": "./src/utils/platform/crypto.web.js",
|
".": "./src/index.ts",
|
||||||
"./src/utils/platform/transport.js": "./src/utils/platform/transport.web.js",
|
"./utils.js": "./src/utils/index.ts",
|
||||||
"./src/utils/platform/logging.js": "./src/utils/platform/logging.web.js",
|
"./client.js": "./src/highlevel/client.ts",
|
||||||
"./src/utils/platform/random.js": "./src/utils/platform/random.web.js",
|
"./methods.js": "./src/highlevel/methods.ts",
|
||||||
"./src/utils/platform/exit-hook.js": "./src/utils/platform/exit-hook.web.js",
|
"./platform.js": "./src/platform.ts"
|
||||||
"./src/highlevel/worker/platform/connect.js": "./src/highlevel/worker/platform/connect.web.js",
|
|
||||||
"./src/highlevel/worker/platform/register.js": "./src/highlevel/worker/platform/register.web.js",
|
|
||||||
"./src/highlevel/methods/files/_platform.js": "./src/highlevel/methods/files/_platform.web.js",
|
|
||||||
"./src/highlevel/methods/files/download-file.js": "./src/highlevel/methods/files/download-file.web.js",
|
|
||||||
"./src/highlevel/utils/platform/storage.js": "./src/highlevel/utils/platform/storage.web.js",
|
|
||||||
"./src/storage/json-file.js": false
|
|
||||||
},
|
},
|
||||||
"distOnlyFields": {
|
"distOnlyFields": {
|
||||||
"exports": {
|
"exports": {
|
||||||
|
@ -35,25 +29,17 @@
|
||||||
"import": "./esm/utils/index.js",
|
"import": "./esm/utils/index.js",
|
||||||
"require": "./cjs/utils/index.js"
|
"require": "./cjs/utils/index.js"
|
||||||
},
|
},
|
||||||
"./utils/crypto/*": {
|
|
||||||
"import": "./esm/utils/crypto/*",
|
|
||||||
"require": "./cjs/utils/crypto/*"
|
|
||||||
},
|
|
||||||
"./network/transports/*": {
|
|
||||||
"import": "./esm/network/transports/*",
|
|
||||||
"require": "./cjs/network/transports/*"
|
|
||||||
},
|
|
||||||
"./storage/*": {
|
|
||||||
"import": "./esm/storage/*",
|
|
||||||
"require": "./cjs/storage/*"
|
|
||||||
},
|
|
||||||
"./highlevel/*": {
|
|
||||||
"import": "./esm/highlevel/*",
|
|
||||||
"require": "./cjs/highlevel/*"
|
|
||||||
},
|
|
||||||
"./methods.js": {
|
"./methods.js": {
|
||||||
"import": "./esm/highlevel/methods.js",
|
"import": "./esm/highlevel/methods.js",
|
||||||
"require": "./cjs/highlevel/methods.js"
|
"require": "./cjs/highlevel/methods.js"
|
||||||
|
},
|
||||||
|
"./platform.js": {
|
||||||
|
"import": "./esm/platform.js",
|
||||||
|
"require": "./cjs/platform.js"
|
||||||
|
},
|
||||||
|
"./client.js": {
|
||||||
|
"import": "./esm/highlevel/client.js",
|
||||||
|
"require": "./cjs/highlevel/client.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -273,6 +273,7 @@ async function addSingleMethod(state, fileName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const isExported = (stmt.modifiers || []).find((mod) => mod.kind === ts.SyntaxKind.ExportKeyword)
|
const isExported = (stmt.modifiers || []).find((mod) => mod.kind === ts.SyntaxKind.ExportKeyword)
|
||||||
|
const isDeclare = (stmt.modifiers || []).find((mod) => mod.kind === ts.SyntaxKind.DeclareKeyword)
|
||||||
const isInitialize = checkForFlag(stmt, '@initialize')
|
const isInitialize = checkForFlag(stmt, '@initialize')
|
||||||
const isManualImpl = checkForFlag(stmt, '@manual-impl')
|
const isManualImpl = checkForFlag(stmt, '@manual-impl')
|
||||||
const isInitializeSuper = isInitialize === 'super'
|
const isInitializeSuper = isInitialize === 'super'
|
||||||
|
@ -327,7 +328,7 @@ async function addSingleMethod(state, fileName) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isExported) continue
|
if (!isExported && !isDeclare) continue
|
||||||
|
|
||||||
const firstArg = stmt.parameters[0]
|
const firstArg = stmt.parameters[0]
|
||||||
|
|
||||||
|
@ -344,7 +345,7 @@ async function addSingleMethod(state, fileName) {
|
||||||
state.methods.used[name] = relPath
|
state.methods.used[name] = relPath
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isExported) {
|
if (isExported || isDeclare) {
|
||||||
const isPrivate = checkForFlag(stmt, '@internal')
|
const isPrivate = checkForFlag(stmt, '@internal')
|
||||||
const isManual = checkForFlag(stmt, '@manual')
|
const isManual = checkForFlag(stmt, '@manual')
|
||||||
const isNoemit = checkForFlag(stmt, '@noemit')
|
const isNoemit = checkForFlag(stmt, '@noemit')
|
||||||
|
@ -358,6 +359,7 @@ async function addSingleMethod(state, fileName) {
|
||||||
isPrivate,
|
isPrivate,
|
||||||
isManual,
|
isManual,
|
||||||
isNoemit,
|
isNoemit,
|
||||||
|
isDeclare,
|
||||||
shouldEmit,
|
shouldEmit,
|
||||||
func: stmt,
|
func: stmt,
|
||||||
comment: getLeadingComments(stmt),
|
comment: getLeadingComments(stmt),
|
||||||
|
@ -369,6 +371,7 @@ async function addSingleMethod(state, fileName) {
|
||||||
hasOverloads: hasOverloads[name] && !isOverload,
|
hasOverloads: hasOverloads[name] && !isOverload,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!isDeclare) {
|
||||||
if (!(module in state.imports)) {
|
if (!(module in state.imports)) {
|
||||||
state.imports[module] = new Set()
|
state.imports[module] = new Set()
|
||||||
}
|
}
|
||||||
|
@ -378,6 +381,7 @@ async function addSingleMethod(state, fileName) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else if (stmt.kind === ts.SyntaxKind.InterfaceDeclaration) {
|
} else if (stmt.kind === ts.SyntaxKind.InterfaceDeclaration) {
|
||||||
if (isCopy) {
|
if (isCopy) {
|
||||||
state.copy.push({
|
state.copy.push({
|
||||||
|
@ -399,6 +403,9 @@ async function addSingleMethod(state, fileName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
state.imports[module].add(stmt.name.escapedText)
|
state.imports[module].add(stmt.name.escapedText)
|
||||||
|
|
||||||
|
state.exported[module] = state.exported[module] || new Set()
|
||||||
|
state.exported[module].add(stmt.name.escapedText)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,6 +436,9 @@ async function addSingleMethod(state, fileName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
state.imports[module].add(stmt.name.escapedText)
|
state.imports[module].add(stmt.name.escapedText)
|
||||||
|
|
||||||
|
state.exported[module] = state.exported[module] || new Set()
|
||||||
|
state.exported[module].add(stmt.name.escapedText)
|
||||||
} else if (isCopy) {
|
} else if (isCopy) {
|
||||||
state.copy.push({ from: relPath, code: stmt.getFullText().trim() })
|
state.copy.push({ from: relPath, code: stmt.getFullText().trim() })
|
||||||
} else if (isTypeExported) {
|
} else if (isTypeExported) {
|
||||||
|
@ -442,6 +452,7 @@ async function main() {
|
||||||
const output = fs.createWriteStream(targetFile)
|
const output = fs.createWriteStream(targetFile)
|
||||||
const state = {
|
const state = {
|
||||||
imports: {},
|
imports: {},
|
||||||
|
exported: {},
|
||||||
fields: [],
|
fields: [],
|
||||||
init: [],
|
init: [],
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -527,6 +538,7 @@ on(name: string, handler: (...args: any[]) => void): this\n`)
|
||||||
available,
|
available,
|
||||||
rawApiMethods,
|
rawApiMethods,
|
||||||
dependencies,
|
dependencies,
|
||||||
|
isDeclare,
|
||||||
}) => {
|
}) => {
|
||||||
if (!available && !overload) {
|
if (!available && !overload) {
|
||||||
// no @available directive
|
// no @available directive
|
||||||
|
@ -659,7 +671,7 @@ on(name: string, handler: (...args: any[]) => void): this\n`)
|
||||||
output.write(`${name}${generics}(${parameters})${returnType}\n`)
|
output.write(`${name}${generics}(${parameters})${returnType}\n`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!overload && !isManual) {
|
if (!overload && !isManual && !isDeclare) {
|
||||||
if (hasOverloads) {
|
if (hasOverloads) {
|
||||||
classProtoDecls.push('// @ts-expect-error this kinda breaks typings for overloads, idc')
|
classProtoDecls.push('// @ts-expect-error this kinda breaks typings for overloads, idc')
|
||||||
}
|
}
|
||||||
|
@ -737,9 +749,14 @@ on(name: string, handler: (...args: any[]) => void): this\n`)
|
||||||
const outputMethods = fs.createWriteStream(targetFileMethods)
|
const outputMethods = fs.createWriteStream(targetFileMethods)
|
||||||
|
|
||||||
outputMethods.write('/* THIS FILE WAS AUTO-GENERATED */\n')
|
outputMethods.write('/* THIS FILE WAS AUTO-GENERATED */\n')
|
||||||
state.methods.list.forEach(({ module, name, overload }) => {
|
state.methods.list.forEach(({ module, name, overload, isDeclare }) => {
|
||||||
if (overload) return
|
if (overload || isDeclare) return
|
||||||
outputMethods.write(`export { ${name} } from '${module}'\n`)
|
outputMethods.write(`export { ${name} } from '${module}'\n`)
|
||||||
|
|
||||||
|
if (state.exported[module]) {
|
||||||
|
outputMethods.write(`export type { ${[...state.exported[module]].join(', ')} } from '${module}'\n`)
|
||||||
|
delete state.exported[module]
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
await new Promise((resolve) => { outputMethods.end(resolve) })
|
await new Promise((resolve) => { outputMethods.end(resolve) })
|
||||||
|
|
|
@ -7,8 +7,7 @@ import Long from 'long'
|
||||||
import { tdFileId } from '@mtcute/file-id'
|
import { tdFileId } from '@mtcute/file-id'
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
|
|
||||||
import { MemoryStorage } from '../storage/providers/memory/index.js'
|
import { MaybeArray, MaybePromise, MtUnsupportedError, PartialExcept, PartialOnly } from '../types/index.js'
|
||||||
import { MaybeArray, MaybePromise, PartialExcept, PartialOnly } from '../types/index.js'
|
|
||||||
import { BaseTelegramClient, BaseTelegramClientOptions } from './base.js'
|
import { BaseTelegramClient, BaseTelegramClientOptions } from './base.js'
|
||||||
import { ITelegramClient } from './client.types.js'
|
import { ITelegramClient } from './client.types.js'
|
||||||
import { checkPassword } from './methods/auth/check-password.js'
|
import { checkPassword } from './methods/auth/check-password.js'
|
||||||
|
@ -95,7 +94,6 @@ import { getPeerDialogs } from './methods/dialogs/get-peer-dialogs.js'
|
||||||
import { iterDialogs } from './methods/dialogs/iter-dialogs.js'
|
import { iterDialogs } from './methods/dialogs/iter-dialogs.js'
|
||||||
import { setFoldersOrder } from './methods/dialogs/set-folders-order.js'
|
import { setFoldersOrder } from './methods/dialogs/set-folders-order.js'
|
||||||
import { downloadAsBuffer } from './methods/files/download-buffer.js'
|
import { downloadAsBuffer } from './methods/files/download-buffer.js'
|
||||||
import { downloadToFile } from './methods/files/download-file.js'
|
|
||||||
import { downloadAsIterable } from './methods/files/download-iterable.js'
|
import { downloadAsIterable } from './methods/files/download-iterable.js'
|
||||||
import { downloadAsStream } from './methods/files/download-stream.js'
|
import { downloadAsStream } from './methods/files/download-stream.js'
|
||||||
import { _normalizeInputFile } from './methods/files/normalize-input-file.js'
|
import { _normalizeInputFile } from './methods/files/normalize-input-file.js'
|
||||||
|
@ -314,21 +312,19 @@ import {
|
||||||
UserTypingUpdate,
|
UserTypingUpdate,
|
||||||
} from './types/index.js'
|
} from './types/index.js'
|
||||||
import { makeParsedUpdateHandler, ParsedUpdateHandlerParams } from './updates/parsed.js'
|
import { makeParsedUpdateHandler, ParsedUpdateHandlerParams } from './updates/parsed.js'
|
||||||
import { _defaultStorageFactory } from './utils/platform/storage.js'
|
|
||||||
import { StringSessionData } from './utils/string-session.js'
|
import { StringSessionData } from './utils/string-session.js'
|
||||||
|
|
||||||
// from methods/_init.ts
|
// from methods/_init.ts
|
||||||
// @copy
|
// @copy
|
||||||
type TelegramClientOptions = (
|
type TelegramClientOptions = (
|
||||||
| (Omit<BaseTelegramClientOptions, 'storage'> & {
|
| (PartialOnly<Omit<BaseTelegramClientOptions, 'storage'>, 'transport' | 'crypto'> & {
|
||||||
/**
|
/**
|
||||||
* Storage to use for this client.
|
* Storage to use for this client.
|
||||||
*
|
*
|
||||||
* If a string is passed, it will be used as:
|
* If a string is passed, it will be used as
|
||||||
* - a path to a JSON file for Node.js
|
* a name for the default platform-specific storage provider to use.
|
||||||
* - IndexedDB database name for browsers
|
|
||||||
*
|
*
|
||||||
* If omitted, {@link MemoryStorage} is used
|
* @default `"client.session"`
|
||||||
*/
|
*/
|
||||||
storage?: string | ITelegramStorageProvider
|
storage?: string | ITelegramStorageProvider
|
||||||
})
|
})
|
||||||
|
@ -2250,6 +2246,7 @@ export interface TelegramClient extends ITelegramClient {
|
||||||
* @param params File download parameters
|
* @param params File download parameters
|
||||||
*/
|
*/
|
||||||
downloadAsBuffer(location: FileDownloadLocation, params?: FileDownloadParameters): Promise<Uint8Array>
|
downloadAsBuffer(location: FileDownloadLocation, params?: FileDownloadParameters): Promise<Uint8Array>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download a remote file to a local file (only for NodeJS).
|
* Download a remote file to a local file (only for NodeJS).
|
||||||
* Promise will resolve once the download is complete.
|
* Promise will resolve once the download is complete.
|
||||||
|
@ -5140,20 +5137,13 @@ export class TelegramClient extends EventEmitter implements ITelegramClient {
|
||||||
if ('client' in opts) {
|
if ('client' in opts) {
|
||||||
this._client = opts.client
|
this._client = opts.client
|
||||||
} else {
|
} else {
|
||||||
let storage: ITelegramStorageProvider
|
if (!opts.storage || typeof opts.storage === 'string' || !opts.transport || !opts.crypto) {
|
||||||
|
throw new MtUnsupportedError(
|
||||||
if (typeof opts.storage === 'string') {
|
'You need to explicitly provide storage, transport and crypto for @mtcute/core',
|
||||||
storage = _defaultStorageFactory(opts.storage)
|
)
|
||||||
} else if (!opts.storage) {
|
|
||||||
storage = new MemoryStorage()
|
|
||||||
} else {
|
|
||||||
storage = opts.storage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._client = new BaseTelegramClient({
|
this._client = new BaseTelegramClient(opts as BaseTelegramClientOptions)
|
||||||
...opts,
|
|
||||||
storage,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-expect-error codegen
|
// @ts-expect-error codegen
|
||||||
|
@ -5448,9 +5438,6 @@ TelegramClient.prototype.setFoldersOrder = function (...args) {
|
||||||
TelegramClient.prototype.downloadAsBuffer = function (...args) {
|
TelegramClient.prototype.downloadAsBuffer = function (...args) {
|
||||||
return downloadAsBuffer(this._client, ...args)
|
return downloadAsBuffer(this._client, ...args)
|
||||||
}
|
}
|
||||||
TelegramClient.prototype.downloadToFile = function (...args) {
|
|
||||||
return downloadToFile(this._client, ...args)
|
|
||||||
}
|
|
||||||
TelegramClient.prototype.downloadAsIterable = function (...args) {
|
TelegramClient.prototype.downloadAsIterable = function (...args) {
|
||||||
return downloadAsIterable(this._client, ...args)
|
return downloadAsIterable(this._client, ...args)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
export * from './base.js'
|
export * from './base.js'
|
||||||
export * from './client.js'
|
|
||||||
export * from './client.types.js'
|
export * from './client.types.js'
|
||||||
export * from './storage/index.js'
|
export * from './storage/index.js'
|
||||||
export * from './types/index.js'
|
export * from './types/index.js'
|
||||||
|
|
|
@ -86,7 +86,6 @@ export { getPeerDialogs } from './methods/dialogs/get-peer-dialogs.js'
|
||||||
export { iterDialogs } from './methods/dialogs/iter-dialogs.js'
|
export { iterDialogs } from './methods/dialogs/iter-dialogs.js'
|
||||||
export { setFoldersOrder } from './methods/dialogs/set-folders-order.js'
|
export { setFoldersOrder } from './methods/dialogs/set-folders-order.js'
|
||||||
export { downloadAsBuffer } from './methods/files/download-buffer.js'
|
export { downloadAsBuffer } from './methods/files/download-buffer.js'
|
||||||
export { downloadToFile } from './methods/files/download-file.js'
|
|
||||||
export { downloadAsIterable } from './methods/files/download-iterable.js'
|
export { downloadAsIterable } from './methods/files/download-iterable.js'
|
||||||
export { downloadAsStream } from './methods/files/download-stream.js'
|
export { downloadAsStream } from './methods/files/download-stream.js'
|
||||||
export { _normalizeInputFile } from './methods/files/normalize-input-file.js'
|
export { _normalizeInputFile } from './methods/files/normalize-input-file.js'
|
||||||
|
@ -96,6 +95,7 @@ export { uploadMedia } from './methods/files/upload-media.js'
|
||||||
export { createForumTopic } from './methods/forums/create-forum-topic.js'
|
export { createForumTopic } from './methods/forums/create-forum-topic.js'
|
||||||
export { deleteForumTopicHistory } from './methods/forums/delete-forum-topic-history.js'
|
export { deleteForumTopicHistory } from './methods/forums/delete-forum-topic-history.js'
|
||||||
export { editForumTopic } from './methods/forums/edit-forum-topic.js'
|
export { editForumTopic } from './methods/forums/edit-forum-topic.js'
|
||||||
|
export type { GetForumTopicsOffset } from './methods/forums/get-forum-topics.js'
|
||||||
export { getForumTopics } from './methods/forums/get-forum-topics.js'
|
export { getForumTopics } from './methods/forums/get-forum-topics.js'
|
||||||
export { getForumTopicsById } from './methods/forums/get-forum-topics-by-id.js'
|
export { getForumTopicsById } from './methods/forums/get-forum-topics-by-id.js'
|
||||||
export { iterForumTopics } from './methods/forums/iter-forum-topics.js'
|
export { iterForumTopics } from './methods/forums/iter-forum-topics.js'
|
||||||
|
@ -109,6 +109,7 @@ export { editInviteLink } from './methods/invite-links/edit-invite-link.js'
|
||||||
export { exportInviteLink } from './methods/invite-links/export-invite-link.js'
|
export { exportInviteLink } from './methods/invite-links/export-invite-link.js'
|
||||||
export { getInviteLink } from './methods/invite-links/get-invite-link.js'
|
export { getInviteLink } from './methods/invite-links/get-invite-link.js'
|
||||||
export { getInviteLinkMembers } from './methods/invite-links/get-invite-link-members.js'
|
export { getInviteLinkMembers } from './methods/invite-links/get-invite-link-members.js'
|
||||||
|
export type { GetInviteLinksOffset } from './methods/invite-links/get-invite-links.js'
|
||||||
export { getInviteLinks } from './methods/invite-links/get-invite-links.js'
|
export { getInviteLinks } from './methods/invite-links/get-invite-links.js'
|
||||||
export { getPrimaryInviteLink } from './methods/invite-links/get-primary-invite-link.js'
|
export { getPrimaryInviteLink } from './methods/invite-links/get-primary-invite-link.js'
|
||||||
export { hideAllJoinRequests } from './methods/invite-links/hide-all-join-requests.js'
|
export { hideAllJoinRequests } from './methods/invite-links/hide-all-join-requests.js'
|
||||||
|
@ -117,15 +118,18 @@ export { iterInviteLinkMembers } from './methods/invite-links/iter-invite-link-m
|
||||||
export { iterInviteLinks } from './methods/invite-links/iter-invite-links.js'
|
export { iterInviteLinks } from './methods/invite-links/iter-invite-links.js'
|
||||||
export { revokeInviteLink } from './methods/invite-links/revoke-invite-link.js'
|
export { revokeInviteLink } from './methods/invite-links/revoke-invite-link.js'
|
||||||
export { closePoll } from './methods/messages/close-poll.js'
|
export { closePoll } from './methods/messages/close-poll.js'
|
||||||
|
export type { DeleteMessagesParams } from './methods/messages/delete-messages.js'
|
||||||
export { deleteMessagesById } from './methods/messages/delete-messages.js'
|
export { deleteMessagesById } from './methods/messages/delete-messages.js'
|
||||||
export { deleteMessages } from './methods/messages/delete-messages.js'
|
export { deleteMessages } from './methods/messages/delete-messages.js'
|
||||||
export { deleteScheduledMessages } from './methods/messages/delete-scheduled-messages.js'
|
export { deleteScheduledMessages } from './methods/messages/delete-scheduled-messages.js'
|
||||||
export { editInlineMessage } from './methods/messages/edit-inline-message.js'
|
export { editInlineMessage } from './methods/messages/edit-inline-message.js'
|
||||||
export { editMessage } from './methods/messages/edit-message.js'
|
export { editMessage } from './methods/messages/edit-message.js'
|
||||||
|
export type { ForwardMessageOptions } from './methods/messages/forward-messages.js'
|
||||||
export { forwardMessagesById } from './methods/messages/forward-messages.js'
|
export { forwardMessagesById } from './methods/messages/forward-messages.js'
|
||||||
export { forwardMessages } from './methods/messages/forward-messages.js'
|
export { forwardMessages } from './methods/messages/forward-messages.js'
|
||||||
export { getCallbackQueryMessage } from './methods/messages/get-callback-query-message.js'
|
export { getCallbackQueryMessage } from './methods/messages/get-callback-query-message.js'
|
||||||
export { getDiscussionMessage } from './methods/messages/get-discussion-message.js'
|
export { getDiscussionMessage } from './methods/messages/get-discussion-message.js'
|
||||||
|
export type { GetHistoryOffset } from './methods/messages/get-history.js'
|
||||||
export { getHistory } from './methods/messages/get-history.js'
|
export { getHistory } from './methods/messages/get-history.js'
|
||||||
export { getMessageByLink } from './methods/messages/get-message-by-link.js'
|
export { getMessageByLink } from './methods/messages/get-message-by-link.js'
|
||||||
export { getMessageGroup } from './methods/messages/get-message-group.js'
|
export { getMessageGroup } from './methods/messages/get-message-group.js'
|
||||||
|
@ -133,6 +137,7 @@ export { getMessageReactionsById } from './methods/messages/get-message-reaction
|
||||||
export { getMessageReactions } from './methods/messages/get-message-reactions.js'
|
export { getMessageReactions } from './methods/messages/get-message-reactions.js'
|
||||||
export { getMessages } from './methods/messages/get-messages.js'
|
export { getMessages } from './methods/messages/get-messages.js'
|
||||||
export { getMessagesUnsafe } from './methods/messages/get-messages-unsafe.js'
|
export { getMessagesUnsafe } from './methods/messages/get-messages-unsafe.js'
|
||||||
|
export type { GetReactionUsersOffset } from './methods/messages/get-reaction-users.js'
|
||||||
export { getReactionUsers } from './methods/messages/get-reaction-users.js'
|
export { getReactionUsers } from './methods/messages/get-reaction-users.js'
|
||||||
export { getReplyTo } from './methods/messages/get-reply-to.js'
|
export { getReplyTo } from './methods/messages/get-reply-to.js'
|
||||||
export { getScheduledMessages } from './methods/messages/get-scheduled-messages.js'
|
export { getScheduledMessages } from './methods/messages/get-scheduled-messages.js'
|
||||||
|
@ -143,7 +148,9 @@ export { iterSearchMessages } from './methods/messages/iter-search-messages.js'
|
||||||
export { pinMessage } from './methods/messages/pin-message.js'
|
export { pinMessage } from './methods/messages/pin-message.js'
|
||||||
export { readHistory } from './methods/messages/read-history.js'
|
export { readHistory } from './methods/messages/read-history.js'
|
||||||
export { readReactions } from './methods/messages/read-reactions.js'
|
export { readReactions } from './methods/messages/read-reactions.js'
|
||||||
|
export type { SearchGlobalOffset } from './methods/messages/search-global.js'
|
||||||
export { searchGlobal } from './methods/messages/search-global.js'
|
export { searchGlobal } from './methods/messages/search-global.js'
|
||||||
|
export type { SearchMessagesOffset } from './methods/messages/search-messages.js'
|
||||||
export { searchMessages } from './methods/messages/search-messages.js'
|
export { searchMessages } from './methods/messages/search-messages.js'
|
||||||
export { answerText } from './methods/messages/send-answer.js'
|
export { answerText } from './methods/messages/send-answer.js'
|
||||||
export { answerMedia } from './methods/messages/send-answer.js'
|
export { answerMedia } from './methods/messages/send-answer.js'
|
||||||
|
@ -151,10 +158,13 @@ export { answerMediaGroup } from './methods/messages/send-answer.js'
|
||||||
export { commentText } from './methods/messages/send-comment.js'
|
export { commentText } from './methods/messages/send-comment.js'
|
||||||
export { commentMedia } from './methods/messages/send-comment.js'
|
export { commentMedia } from './methods/messages/send-comment.js'
|
||||||
export { commentMediaGroup } from './methods/messages/send-comment.js'
|
export { commentMediaGroup } from './methods/messages/send-comment.js'
|
||||||
|
export type { SendCopyParams } from './methods/messages/send-copy.js'
|
||||||
export { sendCopy } from './methods/messages/send-copy.js'
|
export { sendCopy } from './methods/messages/send-copy.js'
|
||||||
|
export type { SendCopyGroupParams } from './methods/messages/send-copy-group.js'
|
||||||
export { sendCopyGroup } from './methods/messages/send-copy-group.js'
|
export { sendCopyGroup } from './methods/messages/send-copy-group.js'
|
||||||
export { sendMedia } from './methods/messages/send-media.js'
|
export { sendMedia } from './methods/messages/send-media.js'
|
||||||
export { sendMediaGroup } from './methods/messages/send-media-group.js'
|
export { sendMediaGroup } from './methods/messages/send-media-group.js'
|
||||||
|
export type { QuoteParamsFrom } from './methods/messages/send-quote.js'
|
||||||
export { quoteWithText } from './methods/messages/send-quote.js'
|
export { quoteWithText } from './methods/messages/send-quote.js'
|
||||||
export { quoteWithMedia } from './methods/messages/send-quote.js'
|
export { quoteWithMedia } from './methods/messages/send-quote.js'
|
||||||
export { quoteWithMediaGroup } from './methods/messages/send-quote.js'
|
export { quoteWithMediaGroup } from './methods/messages/send-quote.js'
|
||||||
|
@ -179,6 +189,7 @@ export { resendPasswordEmail } from './methods/password/password-email.js'
|
||||||
export { cancelPasswordEmail } from './methods/password/password-email.js'
|
export { cancelPasswordEmail } from './methods/password/password-email.js'
|
||||||
export { removeCloudPassword } from './methods/password/remove-cloud-password.js'
|
export { removeCloudPassword } from './methods/password/remove-cloud-password.js'
|
||||||
export { applyBoost } from './methods/premium/apply-boost.js'
|
export { applyBoost } from './methods/premium/apply-boost.js'
|
||||||
|
export type { CanApplyBoostResult } from './methods/premium/can-apply-boost.js'
|
||||||
export { canApplyBoost } from './methods/premium/can-apply-boost.js'
|
export { canApplyBoost } from './methods/premium/can-apply-boost.js'
|
||||||
export { getBoostStats } from './methods/premium/get-boost-stats.js'
|
export { getBoostStats } from './methods/premium/get-boost-stats.js'
|
||||||
export { getBoosts } from './methods/premium/get-boosts.js'
|
export { getBoosts } from './methods/premium/get-boosts.js'
|
||||||
|
@ -194,6 +205,7 @@ export { getStickerSet } from './methods/stickers/get-sticker-set.js'
|
||||||
export { moveStickerInSet } from './methods/stickers/move-sticker-in-set.js'
|
export { moveStickerInSet } from './methods/stickers/move-sticker-in-set.js'
|
||||||
export { setChatStickerSet } from './methods/stickers/set-chat-sticker-set.js'
|
export { setChatStickerSet } from './methods/stickers/set-chat-sticker-set.js'
|
||||||
export { setStickerSetThumb } from './methods/stickers/set-sticker-set-thumb.js'
|
export { setStickerSetThumb } from './methods/stickers/set-sticker-set-thumb.js'
|
||||||
|
export type { CanSendStoryResult } from './methods/stories/can-send-story.js'
|
||||||
export { canSendStory } from './methods/stories/can-send-story.js'
|
export { canSendStory } from './methods/stories/can-send-story.js'
|
||||||
export { deleteStories } from './methods/stories/delete-stories.js'
|
export { deleteStories } from './methods/stories/delete-stories.js'
|
||||||
export { editStory } from './methods/stories/edit-story.js'
|
export { editStory } from './methods/stories/edit-story.js'
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { tdFileId } from '@mtcute/file-id'
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
|
|
||||||
// @copy
|
// @copy
|
||||||
import { MaybeArray, MaybePromise, PartialExcept, PartialOnly } from '../../types/index.js'
|
import { MaybeArray, MaybePromise, MtUnsupportedError, PartialExcept, PartialOnly } from '../../types/index.js'
|
||||||
// @copy
|
// @copy
|
||||||
import { BaseTelegramClient, BaseTelegramClientOptions } from '../base.js'
|
import { BaseTelegramClient, BaseTelegramClientOptions } from '../base.js'
|
||||||
// @copy
|
// @copy
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
|
||||||
// @copy
|
// @copy
|
||||||
import { MemoryStorage } from '../../storage/providers/memory/index.js'
|
import { MtUnsupportedError, PartialOnly } from '../../types/index.js'
|
||||||
import { BaseTelegramClient, BaseTelegramClientOptions } from '../base.js'
|
import { BaseTelegramClient, BaseTelegramClientOptions } from '../base.js'
|
||||||
import { TelegramClient } from '../client.js'
|
import { TelegramClient } from '../client.js'
|
||||||
import { ITelegramClient } from '../client.types.js'
|
import { ITelegramClient } from '../client.types.js'
|
||||||
|
@ -11,19 +11,16 @@ import { ITelegramStorageProvider } from '../storage/provider.js'
|
||||||
import { Conversation } from '../types/conversation.js'
|
import { Conversation } from '../types/conversation.js'
|
||||||
// @copy
|
// @copy
|
||||||
import { makeParsedUpdateHandler, ParsedUpdateHandlerParams } from '../updates/parsed.js'
|
import { makeParsedUpdateHandler, ParsedUpdateHandlerParams } from '../updates/parsed.js'
|
||||||
// @copy
|
|
||||||
import { _defaultStorageFactory } from '../utils/platform/storage.js'
|
|
||||||
|
|
||||||
// @copy
|
// @copy
|
||||||
type TelegramClientOptions = ((Omit<BaseTelegramClientOptions, 'storage'> & {
|
type TelegramClientOptions = ((PartialOnly<Omit<BaseTelegramClientOptions, 'storage'>, 'transport' | 'crypto'> & {
|
||||||
/**
|
/**
|
||||||
* Storage to use for this client.
|
* Storage to use for this client.
|
||||||
*
|
*
|
||||||
* If a string is passed, it will be used as:
|
* If a string is passed, it will be used as
|
||||||
* - a path to a JSON file for Node.js
|
* a name for the default platform-specific storage provider to use.
|
||||||
* - IndexedDB database name for browsers
|
|
||||||
*
|
*
|
||||||
* If omitted, {@link MemoryStorage} is used
|
* @default `"client.session"`
|
||||||
*/
|
*/
|
||||||
storage?: string | ITelegramStorageProvider
|
storage?: string | ITelegramStorageProvider
|
||||||
}) | ({ client: ITelegramClient })) & {
|
}) | ({ client: ITelegramClient })) & {
|
||||||
|
@ -37,41 +34,17 @@ type TelegramClientOptions = ((Omit<BaseTelegramClientOptions, 'storage'> & {
|
||||||
skipConversationUpdates?: boolean
|
skipConversationUpdates?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// // @initialize=super
|
|
||||||
// /** @internal */
|
|
||||||
// function _initializeClientSuper(this: TelegramClient, opts: TelegramClientOptions) {
|
|
||||||
// if (typeof opts.storage === 'string') {
|
|
||||||
// opts.storage = _defaultStorageFactory(opts.storage)
|
|
||||||
// } else if (!opts.storage) {
|
|
||||||
// opts.storage = new MemoryStorage()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
||||||
// // @ts-expect-error codegen
|
|
||||||
// super(opts)
|
|
||||||
// /* eslint-enable @typescript-eslint/no-unsafe-call */
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @initialize
|
// @initialize
|
||||||
/** @internal */
|
/** @internal */
|
||||||
function _initializeClient(this: TelegramClient, opts: TelegramClientOptions) {
|
function _initializeClient(this: TelegramClient, opts: TelegramClientOptions) {
|
||||||
if ('client' in opts) {
|
if ('client' in opts) {
|
||||||
this._client = opts.client
|
this._client = opts.client
|
||||||
} else {
|
} else {
|
||||||
let storage: ITelegramStorageProvider
|
if (!opts.storage || typeof opts.storage === 'string' || !opts.transport || !opts.crypto) {
|
||||||
|
throw new MtUnsupportedError('You need to explicitly provide storage, transport and crypto for @mtcute/core')
|
||||||
if (typeof opts.storage === 'string') {
|
|
||||||
storage = _defaultStorageFactory(opts.storage)
|
|
||||||
} else if (!opts.storage) {
|
|
||||||
storage = new MemoryStorage()
|
|
||||||
} else {
|
|
||||||
storage = opts.storage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._client = new BaseTelegramClient({
|
this._client = new BaseTelegramClient(opts as BaseTelegramClientOptions)
|
||||||
...opts,
|
|
||||||
storage,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-expect-error codegen
|
// @ts-expect-error codegen
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
import { utf8EncodeToBuffer } from '@mtcute/tl-runtime'
|
|
||||||
|
|
||||||
|
import { getPlatform } from '../../../platform.js'
|
||||||
import { ITelegramClient } from '../../client.types.js'
|
import { ITelegramClient } from '../../client.types.js'
|
||||||
import { InputMessageId, normalizeInputMessageId } from '../../types/index.js'
|
import { InputMessageId, normalizeInputMessageId } from '../../types/index.js'
|
||||||
import { resolvePeer } from '../users/resolve-peer.js'
|
import { resolvePeer } from '../users/resolve-peer.js'
|
||||||
|
@ -53,7 +53,7 @@ export async function getCallbackAnswer(
|
||||||
_: 'messages.getBotCallbackAnswer',
|
_: 'messages.getBotCallbackAnswer',
|
||||||
peer: await resolvePeer(client, chatId),
|
peer: await resolvePeer(client, chatId),
|
||||||
msgId: message,
|
msgId: message,
|
||||||
data: typeof data === 'string' ? utf8EncodeToBuffer(data) : data,
|
data: typeof data === 'string' ? getPlatform().utf8Encode(data) : data,
|
||||||
password,
|
password,
|
||||||
game: game,
|
game: game,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
// eslint-disable-next-line no-restricted-imports
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import { createWriteStream, rmSync } from 'fs'
|
|
||||||
import { writeFile } from 'fs/promises'
|
|
||||||
|
|
||||||
import { ITelegramClient } from '../../client.types.js'
|
import { ITelegramClient } from '../../client.types.js'
|
||||||
import { FileDownloadLocation, FileDownloadParameters, FileLocation } from '../../types/index.js'
|
import { FileDownloadLocation, FileDownloadParameters } from '../../types/index.js'
|
||||||
import { downloadAsIterable } from './download-iterable.js'
|
|
||||||
|
|
||||||
|
// @available=both
|
||||||
/**
|
/**
|
||||||
* Download a remote file to a local file (only for NodeJS).
|
* Download a remote file to a local file (only for NodeJS).
|
||||||
* Promise will resolve once the download is complete.
|
* Promise will resolve once the download is complete.
|
||||||
|
@ -13,30 +11,9 @@ import { downloadAsIterable } from './download-iterable.js'
|
||||||
* @param filename Local file name to which the remote file will be downloaded
|
* @param filename Local file name to which the remote file will be downloaded
|
||||||
* @param params File download parameters
|
* @param params File download parameters
|
||||||
*/
|
*/
|
||||||
export async function downloadToFile(
|
declare function downloadToFile(
|
||||||
client: ITelegramClient,
|
client: ITelegramClient,
|
||||||
filename: string,
|
filename: string,
|
||||||
location: FileDownloadLocation,
|
location: FileDownloadLocation,
|
||||||
params?: FileDownloadParameters,
|
params?: FileDownloadParameters,
|
||||||
): Promise<void> {
|
): Promise<void>
|
||||||
if (location instanceof FileLocation && ArrayBuffer.isView(location.location)) {
|
|
||||||
// early return for inline files
|
|
||||||
await writeFile(filename, location.location)
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = createWriteStream(filename)
|
|
||||||
|
|
||||||
if (params?.abortSignal) {
|
|
||||||
params.abortSignal.addEventListener('abort', () => {
|
|
||||||
client.log.debug('aborting file download %s - cleaning up', filename)
|
|
||||||
output.destroy()
|
|
||||||
rmSync(filename)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for await (const chunk of downloadAsIterable(client, location, params)) {
|
|
||||||
output.write(chunk)
|
|
||||||
}
|
|
||||||
|
|
||||||
output.end()
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
import { MtUnsupportedError } from '../../../types/errors.js'
|
|
||||||
|
|
||||||
export function downloadToFile() {
|
|
||||||
throw new MtUnsupportedError('Downloading to file is only supported in NodeJS')
|
|
||||||
}
|
|
|
@ -2,6 +2,7 @@ import { parseFileId } from '@mtcute/file-id'
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
|
|
||||||
import { ConnectionKind } from '../../../network/network-manager.js'
|
import { ConnectionKind } from '../../../network/network-manager.js'
|
||||||
|
import { getPlatform } from '../../../platform.js'
|
||||||
import { MtArgumentError, MtUnsupportedError } from '../../../types/errors.js'
|
import { MtArgumentError, MtUnsupportedError } from '../../../types/errors.js'
|
||||||
import { ConditionVariable } from '../../../utils/condition-variable.js'
|
import { ConditionVariable } from '../../../utils/condition-variable.js'
|
||||||
import { ITelegramClient } from '../../client.types.js'
|
import { ITelegramClient } from '../../client.types.js'
|
||||||
|
@ -56,7 +57,7 @@ export async function* downloadAsIterable(
|
||||||
if (!fileSize) fileSize = input.fileSize
|
if (!fileSize) fileSize = input.fileSize
|
||||||
location = locationInner
|
location = locationInner
|
||||||
} else if (typeof input === 'string') {
|
} else if (typeof input === 'string') {
|
||||||
const parsed = parseFileId(input)
|
const parsed = parseFileId(getPlatform(), input)
|
||||||
|
|
||||||
if (parsed.location._ === 'web') {
|
if (parsed.location._ === 'web') {
|
||||||
location = fileIdToInputWebFileLocation(parsed)
|
location = fileIdToInputWebFileLocation(parsed)
|
||||||
|
|
|
@ -3,6 +3,7 @@ import Long from 'long'
|
||||||
import { parseFileId, tdFileId } from '@mtcute/file-id'
|
import { parseFileId, tdFileId } from '@mtcute/file-id'
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
|
|
||||||
|
import { getPlatform } from '../../../platform.js'
|
||||||
import { assertTypeIs } from '../../../utils/type-assertions.js'
|
import { assertTypeIs } from '../../../utils/type-assertions.js'
|
||||||
import { ITelegramClient } from '../../client.types.js'
|
import { ITelegramClient } from '../../client.types.js'
|
||||||
import { isUploadedFile } from '../../types/files/uploaded-file.js'
|
import { isUploadedFile } from '../../types/files/uploaded-file.js'
|
||||||
|
@ -303,7 +304,7 @@ export async function _normalizeInputMedia(
|
||||||
} else if (typeof input === 'string' && input.match(/^file:/)) {
|
} else if (typeof input === 'string' && input.match(/^file:/)) {
|
||||||
await upload(input.substring(5))
|
await upload(input.substring(5))
|
||||||
} else {
|
} else {
|
||||||
const parsed = typeof input === 'string' ? parseFileId(input) : input
|
const parsed = typeof input === 'string' ? parseFileId(getPlatform(), input) : input
|
||||||
|
|
||||||
if (parsed.location._ === 'photo') {
|
if (parsed.location._ === 'photo') {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { UploadedFile, UploadFileLike } from '../../types/index.js'
|
||||||
import { guessFileMime } from '../../utils/file-type.js'
|
import { guessFileMime } from '../../utils/file-type.js'
|
||||||
import { determinePartSize, isProbablyPlainText } from '../../utils/file-utils.js'
|
import { determinePartSize, isProbablyPlainText } from '../../utils/file-utils.js'
|
||||||
import { bufferToStream, createChunkedReader, streamToBuffer } from '../../utils/stream-utils.js'
|
import { bufferToStream, createChunkedReader, streamToBuffer } from '../../utils/stream-utils.js'
|
||||||
import { _createFileStream, _extractFileStreamMeta, _handleNodeStream, _isFileStream } from './_platform.js'
|
|
||||||
|
|
||||||
const OVERRIDE_MIME: Record<string, string> = {
|
const OVERRIDE_MIME: Record<string, string> = {
|
||||||
// tg doesn't interpret `audio/opus` files as voice messages for some reason
|
// tg doesn't interpret `audio/opus` files as voice messages for some reason
|
||||||
|
@ -37,9 +36,6 @@ export async function uploadFile(
|
||||||
params: {
|
params: {
|
||||||
/**
|
/**
|
||||||
* Upload file source.
|
* Upload file source.
|
||||||
*
|
|
||||||
* > **Note**: `fs.ReadStream` is a subclass of `stream.Readable` and contains
|
|
||||||
* > info about file name, thus you don't need to pass them explicitly.
|
|
||||||
*/
|
*/
|
||||||
file: UploadFileLike
|
file: UploadFileLike
|
||||||
|
|
||||||
|
@ -113,19 +109,9 @@ export async function uploadFile(
|
||||||
if (typeof File !== 'undefined' && file instanceof File) {
|
if (typeof File !== 'undefined' && file instanceof File) {
|
||||||
fileName = file.name
|
fileName = file.name
|
||||||
fileSize = file.size
|
fileSize = file.size
|
||||||
// file is now ReadableStream
|
|
||||||
file = file.stream()
|
file = file.stream()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof file === 'string') {
|
|
||||||
file = _createFileStream(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_isFileStream(file)) {
|
|
||||||
[fileName, fileSize] = await _extractFileStreamMeta(file)
|
|
||||||
// fs.ReadStream is a subclass of Readable, will be handled below
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof file === 'object' && 'headers' in file && 'body' in file && 'url' in file) {
|
if (typeof file === 'object' && 'headers' in file && 'body' in file && 'url' in file) {
|
||||||
// fetch() response
|
// fetch() response
|
||||||
const length = parseInt(file.headers.get('content-length') || '0')
|
const length = parseInt(file.headers.get('content-length') || '0')
|
||||||
|
@ -161,8 +147,6 @@ export async function uploadFile(
|
||||||
file = file.body
|
file = file.body
|
||||||
}
|
}
|
||||||
|
|
||||||
file = _handleNodeStream(file)
|
|
||||||
|
|
||||||
if (!(file instanceof ReadableStream)) {
|
if (!(file instanceof ReadableStream)) {
|
||||||
throw new MtArgumentError('Could not convert input `file` to stream!')
|
throw new MtArgumentError('Could not convert input `file` to stream!')
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
import { utf8EncodeToBuffer } from '@mtcute/tl-runtime'
|
|
||||||
|
|
||||||
|
import { getPlatform } from '../../../platform.js'
|
||||||
import { assertNever } from '../../../types/utils.js'
|
import { assertNever } from '../../../types/utils.js'
|
||||||
import { toInputUser } from '../../utils/peer-utils.js'
|
import { toInputUser } from '../../utils/peer-utils.js'
|
||||||
import { BotKeyboardBuilder } from './keyboard-builder.js'
|
import { BotKeyboardBuilder } from './keyboard-builder.js'
|
||||||
|
@ -218,7 +218,7 @@ export namespace BotKeyboard {
|
||||||
_: 'keyboardButtonCallback',
|
_: 'keyboardButtonCallback',
|
||||||
text,
|
text,
|
||||||
requiresPassword,
|
requiresPassword,
|
||||||
data: typeof data === 'string' ? utf8EncodeToBuffer(data) : data,
|
data: typeof data === 'string' ? getPlatform().utf8Encode(data) : data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { tdFileId as td, toFileId, toUniqueFileId } from '@mtcute/file-id'
|
import { tdFileId as td, toFileId, toUniqueFileId } from '@mtcute/file-id'
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
|
|
||||||
|
import { getPlatform } from '../../../platform.js'
|
||||||
import { makeInspectable } from '../../utils/index.js'
|
import { makeInspectable } from '../../utils/index.js'
|
||||||
import { memoizeGetters } from '../../utils/memoize.js'
|
import { memoizeGetters } from '../../utils/memoize.js'
|
||||||
import { FileLocation } from '../files/index.js'
|
import { FileLocation } from '../files/index.js'
|
||||||
|
@ -123,7 +124,7 @@ export abstract class RawDocument extends FileLocation {
|
||||||
* representing this document.
|
* representing this document.
|
||||||
*/
|
*/
|
||||||
get fileId(): string {
|
get fileId(): string {
|
||||||
return toFileId({
|
return toFileId(getPlatform(), {
|
||||||
type: this._fileIdType(),
|
type: this._fileIdType(),
|
||||||
dcId: this.raw.dcId,
|
dcId: this.raw.dcId,
|
||||||
fileReference: this.raw.fileReference,
|
fileReference: this.raw.fileReference,
|
||||||
|
@ -139,7 +140,7 @@ export abstract class RawDocument extends FileLocation {
|
||||||
* Get a unique File ID representing this document.
|
* Get a unique File ID representing this document.
|
||||||
*/
|
*/
|
||||||
get uniqueFileId(): string {
|
get uniqueFileId(): string {
|
||||||
return toUniqueFileId(td.FileType.Document, {
|
return toUniqueFileId(getPlatform(), td.FileType.Document, {
|
||||||
_: 'common',
|
_: 'common',
|
||||||
id: this.raw.id,
|
id: this.raw.id,
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,6 +3,7 @@ import Long from 'long'
|
||||||
import { tdFileId as td, toFileId, toUniqueFileId } from '@mtcute/file-id'
|
import { tdFileId as td, toFileId, toUniqueFileId } from '@mtcute/file-id'
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
|
|
||||||
|
import { getPlatform } from '../../../platform.js'
|
||||||
import { MtArgumentError, MtTypeAssertionError } from '../../../types/errors.js'
|
import { MtArgumentError, MtTypeAssertionError } from '../../../types/errors.js'
|
||||||
import { assertTypeIs } from '../../../utils/type-assertions.js'
|
import { assertTypeIs } from '../../../utils/type-assertions.js'
|
||||||
import { inflateSvgPath, strippedPhotoToJpg, svgPathToFile } from '../../utils/file-utils.js'
|
import { inflateSvgPath, strippedPhotoToJpg, svgPathToFile } from '../../utils/file-utils.js'
|
||||||
|
@ -192,7 +193,7 @@ export class Thumbnail extends FileLocation {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._media._ === 'stickerSet') {
|
if (this._media._ === 'stickerSet') {
|
||||||
return toFileId({
|
return toFileId(getPlatform(), {
|
||||||
type: td.FileType.Thumbnail,
|
type: td.FileType.Thumbnail,
|
||||||
dcId: this.dcId!,
|
dcId: this.dcId!,
|
||||||
fileReference: null,
|
fileReference: null,
|
||||||
|
@ -210,7 +211,7 @@ export class Thumbnail extends FileLocation {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return toFileId({
|
return toFileId(getPlatform(), {
|
||||||
type: this._media._ === 'photo' ? td.FileType.Photo : td.FileType.Thumbnail,
|
type: this._media._ === 'photo' ? td.FileType.Photo : td.FileType.Thumbnail,
|
||||||
dcId: this.dcId!,
|
dcId: this.dcId!,
|
||||||
fileReference: this._media.fileReference,
|
fileReference: this._media.fileReference,
|
||||||
|
@ -239,7 +240,7 @@ export class Thumbnail extends FileLocation {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._media._ === 'stickerSet') {
|
if (this._media._ === 'stickerSet') {
|
||||||
return toUniqueFileId(td.FileType.Thumbnail, {
|
return toUniqueFileId(getPlatform(), td.FileType.Thumbnail, {
|
||||||
_: 'photo',
|
_: 'photo',
|
||||||
id: Long.ZERO,
|
id: Long.ZERO,
|
||||||
source: {
|
source: {
|
||||||
|
@ -251,7 +252,7 @@ export class Thumbnail extends FileLocation {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return toUniqueFileId(this._media._ === 'photo' ? td.FileType.Photo : td.FileType.Thumbnail, {
|
return toUniqueFileId(getPlatform(), this._media._ === 'photo' ? td.FileType.Photo : td.FileType.Thumbnail, {
|
||||||
_: 'photo',
|
_: 'photo',
|
||||||
id: this._media.id,
|
id: this._media.id,
|
||||||
source: {
|
source: {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import Long from 'long'
|
||||||
import { tdFileId, toFileId, toUniqueFileId } from '@mtcute/file-id'
|
import { tdFileId, toFileId, toUniqueFileId } from '@mtcute/file-id'
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
|
|
||||||
|
import { getPlatform } from '../../../platform.js'
|
||||||
import { MtArgumentError } from '../../../types/errors.js'
|
import { MtArgumentError } from '../../../types/errors.js'
|
||||||
import { toggleChannelIdMark } from '../../../utils/peer-utils.js'
|
import { toggleChannelIdMark } from '../../../utils/peer-utils.js'
|
||||||
import { strippedPhotoToJpg } from '../../utils/file-utils.js'
|
import { strippedPhotoToJpg } from '../../utils/file-utils.js'
|
||||||
|
@ -62,7 +63,7 @@ export class ChatPhotoSize extends FileLocation {
|
||||||
throw new MtArgumentError('Input peer was invalid')
|
throw new MtArgumentError('Input peer was invalid')
|
||||||
}
|
}
|
||||||
|
|
||||||
return toFileId({
|
return toFileId(getPlatform(), {
|
||||||
dcId: this.obj.dcId,
|
dcId: this.obj.dcId,
|
||||||
type: tdFileId.FileType.ProfilePhoto,
|
type: tdFileId.FileType.ProfilePhoto,
|
||||||
fileReference: null,
|
fileReference: null,
|
||||||
|
@ -84,7 +85,7 @@ export class ChatPhotoSize extends FileLocation {
|
||||||
* TDLib and Bot API compatible unique File ID representing this size
|
* TDLib and Bot API compatible unique File ID representing this size
|
||||||
*/
|
*/
|
||||||
get uniqueFileId(): string {
|
get uniqueFileId(): string {
|
||||||
return toUniqueFileId(tdFileId.FileType.ProfilePhoto, {
|
return toUniqueFileId(getPlatform(), tdFileId.FileType.ProfilePhoto, {
|
||||||
_: 'photo',
|
_: 'photo',
|
||||||
id: this.obj.photoId,
|
id: this.obj.photoId,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
import { utf8Decode } from '@mtcute/tl-runtime'
|
|
||||||
|
|
||||||
|
import { getPlatform } from '../../../platform.js'
|
||||||
import { MtArgumentError } from '../../../types/errors.js'
|
import { MtArgumentError } from '../../../types/errors.js'
|
||||||
import { makeInspectable } from '../../utils/index.js'
|
import { makeInspectable } from '../../utils/index.js'
|
||||||
import { encodeInlineMessageId } from '../../utils/inline-utils.js'
|
import { encodeInlineMessageId } from '../../utils/inline-utils.js'
|
||||||
|
@ -58,7 +58,7 @@ class BaseCallbackQuery {
|
||||||
get dataStr(): string | null {
|
get dataStr(): string | null {
|
||||||
if (!this.raw.data) return null
|
if (!this.raw.data) return null
|
||||||
|
|
||||||
return utf8Decode(this.raw.data)
|
return getPlatform().utf8Decode(this.raw.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { tl } from '@mtcute/tl'
|
||||||
import { parseMarkedPeerId } from '../../utils/peer-utils.js'
|
import { parseMarkedPeerId } from '../../utils/peer-utils.js'
|
||||||
|
|
||||||
import FileType = td.FileType
|
import FileType = td.FileType
|
||||||
|
import { getPlatform } from '../../platform.js'
|
||||||
import { assertNever } from '../../types/utils.js'
|
import { assertNever } from '../../types/utils.js'
|
||||||
|
|
||||||
const EMPTY_BUFFER = new Uint8Array(0)
|
const EMPTY_BUFFER = new Uint8Array(0)
|
||||||
|
@ -45,7 +46,7 @@ function dialogPhotoToInputPeer(
|
||||||
* @param fileId File ID, either parsed or as a string
|
* @param fileId File ID, either parsed or as a string
|
||||||
*/
|
*/
|
||||||
export function fileIdToInputWebFileLocation(fileId: string | FileId): tl.RawInputWebFileLocation {
|
export function fileIdToInputWebFileLocation(fileId: string | FileId): tl.RawInputWebFileLocation {
|
||||||
if (typeof fileId === 'string') fileId = parseFileId(fileId)
|
if (typeof fileId === 'string') fileId = parseFileId(getPlatform(), fileId)
|
||||||
|
|
||||||
if (fileId.location._ !== 'web') {
|
if (fileId.location._ !== 'web') {
|
||||||
throw new td.ConversionError('inputWebFileLocation')
|
throw new td.ConversionError('inputWebFileLocation')
|
||||||
|
@ -65,7 +66,7 @@ export function fileIdToInputWebFileLocation(fileId: string | FileId): tl.RawInp
|
||||||
* @param fileId File ID, either parsed or as a string
|
* @param fileId File ID, either parsed or as a string
|
||||||
*/
|
*/
|
||||||
export function fileIdToInputFileLocation(fileId: string | FileId): tl.TypeInputFileLocation {
|
export function fileIdToInputFileLocation(fileId: string | FileId): tl.TypeInputFileLocation {
|
||||||
if (typeof fileId === 'string') fileId = parseFileId(fileId)
|
if (typeof fileId === 'string') fileId = parseFileId(getPlatform(), fileId)
|
||||||
|
|
||||||
const loc = fileId.location
|
const loc = fileId.location
|
||||||
|
|
||||||
|
@ -219,7 +220,7 @@ export function fileIdToInputFileLocation(fileId: string | FileId): tl.TypeInput
|
||||||
* @param fileId File ID, either parsed or as a string
|
* @param fileId File ID, either parsed or as a string
|
||||||
*/
|
*/
|
||||||
export function fileIdToInputDocument(fileId: string | FileId): tl.RawInputDocument {
|
export function fileIdToInputDocument(fileId: string | FileId): tl.RawInputDocument {
|
||||||
if (typeof fileId === 'string') fileId = parseFileId(fileId)
|
if (typeof fileId === 'string') fileId = parseFileId(getPlatform(), fileId)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
fileId.location._ !== 'common' ||
|
fileId.location._ !== 'common' ||
|
||||||
|
@ -256,7 +257,7 @@ export function fileIdToInputDocument(fileId: string | FileId): tl.RawInputDocum
|
||||||
* @param fileId File ID, either parsed or as a string
|
* @param fileId File ID, either parsed or as a string
|
||||||
*/
|
*/
|
||||||
export function fileIdToInputPhoto(fileId: string | FileId): tl.RawInputPhoto {
|
export function fileIdToInputPhoto(fileId: string | FileId): tl.RawInputPhoto {
|
||||||
if (typeof fileId === 'string') fileId = parseFileId(fileId)
|
if (typeof fileId === 'string') fileId = parseFileId(getPlatform(), fileId)
|
||||||
|
|
||||||
if (fileId.location._ !== 'photo') {
|
if (fileId.location._ !== 'photo') {
|
||||||
throw new td.ConversionError('inputPhoto')
|
throw new td.ConversionError('inputPhoto')
|
||||||
|
@ -281,7 +282,7 @@ export function fileIdToInputPhoto(fileId: string | FileId): tl.RawInputPhoto {
|
||||||
* @param fileId File ID, either parsed or as a string
|
* @param fileId File ID, either parsed or as a string
|
||||||
*/
|
*/
|
||||||
export function fileIdToEncryptedFile(fileId: string | FileId): tl.RawInputEncryptedFile {
|
export function fileIdToEncryptedFile(fileId: string | FileId): tl.RawInputEncryptedFile {
|
||||||
if (typeof fileId === 'string') fileId = parseFileId(fileId)
|
if (typeof fileId === 'string') fileId = parseFileId(getPlatform(), fileId)
|
||||||
|
|
||||||
if (fileId.location._ !== 'common' || fileId.type !== FileType.Encrypted) {
|
if (fileId.location._ !== 'common' || fileId.type !== FileType.Encrypted) {
|
||||||
throw new td.ConversionError('inputEncryptedFile')
|
throw new td.ConversionError('inputEncryptedFile')
|
||||||
|
@ -301,7 +302,7 @@ export function fileIdToEncryptedFile(fileId: string | FileId): tl.RawInputEncry
|
||||||
* @param fileId File ID, either parsed or as a string
|
* @param fileId File ID, either parsed or as a string
|
||||||
*/
|
*/
|
||||||
export function fileIdToSecureFile(fileId: string | FileId): tl.RawInputSecureFile {
|
export function fileIdToSecureFile(fileId: string | FileId): tl.RawInputSecureFile {
|
||||||
if (typeof fileId === 'string') fileId = parseFileId(fileId)
|
if (typeof fileId === 'string') fileId = parseFileId(getPlatform(), fileId)
|
||||||
|
|
||||||
if (fileId.location._ !== 'common' || (fileId.type !== FileType.Secure && fileId.type !== FileType.SecureRaw)) {
|
if (fileId.location._ !== 'common' || (fileId.type !== FileType.Secure && fileId.type !== FileType.SecureRaw)) {
|
||||||
throw new td.ConversionError('inputSecureFile')
|
throw new td.ConversionError('inputSecureFile')
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import { hexDecodeToBuffer } from '@mtcute/tl-runtime'
|
import { getPlatform } from '../../platform.js'
|
||||||
|
|
||||||
import { guessFileMime } from './file-type.js'
|
import { guessFileMime } from './file-type.js'
|
||||||
|
|
||||||
|
const p = getPlatform()
|
||||||
|
|
||||||
describe('guessFileMime', () => {
|
describe('guessFileMime', () => {
|
||||||
it.each([
|
it.each([
|
||||||
['424d', 'image/bmp'],
|
['424d', 'image/bmp'],
|
||||||
|
@ -60,6 +61,6 @@ describe('guessFileMime', () => {
|
||||||
])('should detect %s as %s', (header, mime) => {
|
])('should detect %s as %s', (header, mime) => {
|
||||||
header += '00'.repeat(16)
|
header += '00'.repeat(16)
|
||||||
|
|
||||||
expect(guessFileMime(hexDecodeToBuffer(header))).toEqual(mime)
|
expect(guessFileMime(p.hexDecode(header))).toEqual(mime)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import { hexDecodeToBuffer, hexEncode, utf8Decode, utf8EncodeToBuffer } from '@mtcute/tl-runtime'
|
import { getPlatform } from '../../platform.js'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
extractFileName,
|
extractFileName,
|
||||||
inflateSvgPath,
|
inflateSvgPath,
|
||||||
|
@ -10,28 +9,30 @@ import {
|
||||||
svgPathToFile,
|
svgPathToFile,
|
||||||
} from './file-utils.js'
|
} from './file-utils.js'
|
||||||
|
|
||||||
|
const p = getPlatform()
|
||||||
|
|
||||||
describe('isProbablyPlainText', () => {
|
describe('isProbablyPlainText', () => {
|
||||||
it('should return true for buffers only containing printable ascii', () => {
|
it('should return true for buffers only containing printable ascii', () => {
|
||||||
expect(isProbablyPlainText(utf8EncodeToBuffer('hello this is some ascii text'))).to.be.true
|
expect(isProbablyPlainText(p.utf8Encode('hello this is some ascii text'))).to.be.true
|
||||||
expect(isProbablyPlainText(utf8EncodeToBuffer('hello this is some ascii text\nwith unix new lines'))).to.be.true
|
expect(isProbablyPlainText(p.utf8Encode('hello this is some ascii text\nwith unix new lines'))).to.be.true
|
||||||
expect(isProbablyPlainText(utf8EncodeToBuffer('hello this is some ascii text\r\nwith windows new lines'))).to.be
|
expect(isProbablyPlainText(p.utf8Encode('hello this is some ascii text\r\nwith windows new lines'))).to.be
|
||||||
.true
|
.true
|
||||||
expect(isProbablyPlainText(utf8EncodeToBuffer('hello this is some ascii text\n\twith unix new lines and tabs')))
|
expect(isProbablyPlainText(p.utf8Encode('hello this is some ascii text\n\twith unix new lines and tabs')))
|
||||||
.to.be.true
|
.to.be.true
|
||||||
expect(
|
expect(
|
||||||
isProbablyPlainText(
|
isProbablyPlainText(
|
||||||
utf8EncodeToBuffer('hello this is some ascii text\r\n\twith windows new lines and tabs'),
|
p.utf8Encode('hello this is some ascii text\r\n\twith windows new lines and tabs'),
|
||||||
),
|
),
|
||||||
).to.be.true
|
).to.be.true
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return false for buffers containing some binary data', () => {
|
it('should return false for buffers containing some binary data', () => {
|
||||||
expect(isProbablyPlainText(utf8EncodeToBuffer('hello this is cedilla: ç'))).to.be.false
|
expect(isProbablyPlainText(p.utf8Encode('hello this is cedilla: ç'))).to.be.false
|
||||||
expect(isProbablyPlainText(utf8EncodeToBuffer('hello this is some ascii text with emojis 🌸'))).to.be.false
|
expect(isProbablyPlainText(p.utf8Encode('hello this is some ascii text with emojis 🌸'))).to.be.false
|
||||||
|
|
||||||
// random strings of 16 bytes
|
// random strings of 16 bytes
|
||||||
expect(isProbablyPlainText(hexDecodeToBuffer('717f80f08eb9d88c3931712c0e2be32f'))).to.be.false
|
expect(isProbablyPlainText(p.hexDecode('717f80f08eb9d88c3931712c0e2be32f'))).to.be.false
|
||||||
expect(isProbablyPlainText(hexDecodeToBuffer('20e8e218e54254c813b261432b0330d7'))).to.be.false
|
expect(isProbablyPlainText(p.hexDecode('20e8e218e54254c813b261432b0330d7'))).to.be.false
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -53,14 +54,14 @@ describe('svgPathToFile', () => {
|
||||||
it('should convert SVG path to a file', () => {
|
it('should convert SVG path to a file', () => {
|
||||||
const path = 'M 0 0 L 100 0 L 100 100 L 0 100 L 0 0 Z'
|
const path = 'M 0 0 L 100 0 L 100 100 L 0 100 L 0 0 Z'
|
||||||
|
|
||||||
expect(utf8Decode(svgPathToFile(path))).toMatchInlineSnapshot(
|
expect(p.utf8Decode(svgPathToFile(path))).toMatchInlineSnapshot(
|
||||||
'"<?xml version=\\"1.0\\" encoding=\\"utf-8\\"?><svg version=\\"1.1\\" xmlns=\\"http://www.w3.org/2000/svg\\" xmlns:xlink=\\"http://www.w3.org/1999/xlink\\"viewBox=\\"0 0 512 512\\" xml:space=\\"preserve\\"><path d=\\"M 0 0 L 100 0 L 100 100 L 0 100 L 0 0 Z\\"/></svg>"',
|
'"<?xml version=\\"1.0\\" encoding=\\"utf-8\\"?><svg version=\\"1.1\\" xmlns=\\"http://www.w3.org/2000/svg\\" xmlns:xlink=\\"http://www.w3.org/1999/xlink\\"viewBox=\\"0 0 512 512\\" xml:space=\\"preserve\\"><path d=\\"M 0 0 L 100 0 L 100 100 L 0 100 L 0 0 Z\\"/></svg>"',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('inflateSvgPath', () => {
|
describe('inflateSvgPath', () => {
|
||||||
const data = hexDecodeToBuffer(
|
const data = p.hexDecode(
|
||||||
'1a05b302dc5f4446068649064247424a6a4c704550535b5e665e5e4c044a024c' +
|
'1a05b302dc5f4446068649064247424a6a4c704550535b5e665e5e4c044a024c' +
|
||||||
'074e06414d80588863935fad74be4704854684518b528581904695498b488b56' +
|
'074e06414d80588863935fad74be4704854684518b528581904695498b488b56' +
|
||||||
'965c85438d8191818543894a8f4d834188818a4284498454895d9a6f86074708' +
|
'965c85438d8191818543894a8f4d834188818a4284498454895d9a6f86074708' +
|
||||||
|
@ -85,9 +86,9 @@ describe('inflateSvgPath', () => {
|
||||||
|
|
||||||
describe('strippedPhotoToJpg', () => {
|
describe('strippedPhotoToJpg', () => {
|
||||||
// strippedThumb of @Channel_Bot
|
// strippedThumb of @Channel_Bot
|
||||||
const dataPfp = hexDecodeToBuffer('010808b1f2f95fed673451457033ad1f')
|
const dataPfp = p.hexDecode('010808b1f2f95fed673451457033ad1f')
|
||||||
// photoStrippedSize of a random image
|
// photoStrippedSize of a random image
|
||||||
const dataPicture = hexDecodeToBuffer(
|
const dataPicture = p.hexDecode(
|
||||||
'012728b532aacce4b302d8c1099c74a634718675cb6381f73d3ffd557667d9b5' +
|
'012728b532aacce4b302d8c1099c74a634718675cb6381f73d3ffd557667d9b5' +
|
||||||
'816f4c28ce69aa58a863238cf62a334590f999042234cbe1986d03eefe14c68e' +
|
'816f4c28ce69aa58a863238cf62a334590f999042234cbe1986d03eefe14c68e' +
|
||||||
'32847cc00ce709ea7ffad577773f78fe54d6c927f78c3db14ac1ccca91a2ef4f' +
|
'32847cc00ce709ea7ffad577773f78fe54d6c927f78c3db14ac1ccca91a2ef4f' +
|
||||||
|
@ -99,7 +100,7 @@ describe('strippedPhotoToJpg', () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
it('should inflate stripped jpeg (from profile picture)', () => {
|
it('should inflate stripped jpeg (from profile picture)', () => {
|
||||||
expect(hexEncode(strippedPhotoToJpg(dataPfp))).toMatchInlineSnapshot(
|
expect(p.hexEncode(strippedPhotoToJpg(dataPfp))).toMatchInlineSnapshot(
|
||||||
'"ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e192' +
|
'"ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e192' +
|
||||||
'82321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a' +
|
'82321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a' +
|
||||||
'0aadaad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2' +
|
'0aadaad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2' +
|
||||||
|
@ -124,7 +125,7 @@ describe('strippedPhotoToJpg', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should inflate stripped jpeg (from a picture)', () => {
|
it('should inflate stripped jpeg (from a picture)', () => {
|
||||||
expect(hexEncode(strippedPhotoToJpg(dataPicture))).toMatchInlineSnapshot(
|
expect(p.hexEncode(strippedPhotoToJpg(dataPicture))).toMatchInlineSnapshot(
|
||||||
'"ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e192' +
|
'"ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e192' +
|
||||||
'82321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a' +
|
'82321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a' +
|
||||||
'0aadaad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2' +
|
'0aadaad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2' +
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { hexDecodeToBuffer, utf8EncodeToBuffer } from '@mtcute/tl-runtime'
|
import { getPlatform } from '../../platform.js'
|
||||||
|
|
||||||
import { MtArgumentError } from '../../types/errors.js'
|
import { MtArgumentError } from '../../types/errors.js'
|
||||||
import { concatBuffers } from '../../utils/buffer-utils.js'
|
import { concatBuffers } from '../../utils/buffer-utils.js'
|
||||||
|
|
||||||
|
@ -33,7 +32,7 @@ export function isProbablyPlainText(buf: Uint8Array): boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
// from https://github.com/telegramdesktop/tdesktop/blob/bec39d89e19670eb436dc794a8f20b657cb87c71/Telegram/SourceFiles/ui/image/image.cpp#L225
|
// from https://github.com/telegramdesktop/tdesktop/blob/bec39d89e19670eb436dc794a8f20b657cb87c71/Telegram/SourceFiles/ui/image/image.cpp#L225
|
||||||
const JPEG_HEADER = hexDecodeToBuffer(
|
const JPEG_HEADER = () => getPlatform().hexDecode(
|
||||||
'ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e1928' +
|
'ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e1928' +
|
||||||
'2321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aad' +
|
'2321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aad' +
|
||||||
'aad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c35' +
|
'aad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c35' +
|
||||||
|
@ -54,6 +53,7 @@ const JPEG_HEADER = hexDecodeToBuffer(
|
||||||
'b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f' +
|
'b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f' +
|
||||||
'3f4f5f6f7f8f9faffda000c03010002110311003f00',
|
'3f4f5f6f7f8f9faffda000c03010002110311003f00',
|
||||||
)
|
)
|
||||||
|
let JPEG_HEADER_BYTES: Uint8Array | null = null
|
||||||
const JPEG_FOOTER = new Uint8Array([0xff, 0xd9])
|
const JPEG_FOOTER = new Uint8Array([0xff, 0xd9])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -64,7 +64,11 @@ export function strippedPhotoToJpg(stripped: Uint8Array): Uint8Array {
|
||||||
throw new MtArgumentError('Invalid stripped JPEG')
|
throw new MtArgumentError('Invalid stripped JPEG')
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = concatBuffers([JPEG_HEADER, stripped.slice(3), JPEG_FOOTER])
|
if (JPEG_HEADER_BYTES === null) {
|
||||||
|
JPEG_HEADER_BYTES = JPEG_HEADER()
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = concatBuffers([JPEG_HEADER_BYTES, stripped.slice(3), JPEG_FOOTER])
|
||||||
result[164] = stripped[1]
|
result[164] = stripped[1]
|
||||||
result[166] = stripped[2]
|
result[166] = stripped[2]
|
||||||
|
|
||||||
|
@ -108,7 +112,7 @@ export function inflateSvgPath(encoded: Uint8Array): string {
|
||||||
* @param path
|
* @param path
|
||||||
*/
|
*/
|
||||||
export function svgPathToFile(path: string): Uint8Array {
|
export function svgPathToFile(path: string): Uint8Array {
|
||||||
return utf8EncodeToBuffer(
|
return getPlatform().utf8Encode(
|
||||||
'<?xml version="1.0" encoding="utf-8"?>' +
|
'<?xml version="1.0" encoding="utf-8"?>' +
|
||||||
'<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"' +
|
'<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"' +
|
||||||
'viewBox="0 0 512 512" xml:space="preserve">' +
|
'viewBox="0 0 512 512" xml:space="preserve">' +
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
import { base64DecodeToBuffer, base64Encode, TlBinaryReader, TlBinaryWriter } from '@mtcute/tl-runtime'
|
import { TlBinaryReader, TlBinaryWriter } from '@mtcute/tl-runtime'
|
||||||
|
|
||||||
|
import { getPlatform } from '../../platform.js'
|
||||||
import { assertNever } from '../../types/utils.js'
|
import { assertNever } from '../../types/utils.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -9,7 +10,7 @@ import { assertNever } from '../../types/utils.js'
|
||||||
* @param id Inline message ID
|
* @param id Inline message ID
|
||||||
*/
|
*/
|
||||||
export function parseInlineMessageId(id: string): tl.TypeInputBotInlineMessageID {
|
export function parseInlineMessageId(id: string): tl.TypeInputBotInlineMessageID {
|
||||||
const buf = base64DecodeToBuffer(id, true)
|
const buf = getPlatform().base64Decode(id, true)
|
||||||
const reader = TlBinaryReader.manual(buf)
|
const reader = TlBinaryReader.manual(buf)
|
||||||
|
|
||||||
if (buf.length === 20) {
|
if (buf.length === 20) {
|
||||||
|
@ -56,7 +57,7 @@ export function encodeInlineMessageId(id: tl.TypeInputBotInlineMessageID): strin
|
||||||
assertNever(id)
|
assertNever(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
return base64Encode(writer.result(), true)
|
return getPlatform().base64Encode(writer.result(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeInlineId(id: string | tl.TypeInputBotInlineMessageID) {
|
export function normalizeInlineId(id: string | tl.TypeInputBotInlineMessageID) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-assignment */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-argument */
|
/* eslint-disable @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-argument */
|
||||||
|
|
||||||
import { base64Encode } from '@mtcute/tl-runtime'
|
import { getPlatform } from '../../platform.js'
|
||||||
|
|
||||||
const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom')
|
const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom')
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ export function makeInspectable<T>(obj: new (...args: any[]) => T, props?: (keyo
|
||||||
|
|
||||||
if (val && typeof val === 'object') {
|
if (val && typeof val === 'object') {
|
||||||
if (val instanceof Uint8Array) {
|
if (val instanceof Uint8Array) {
|
||||||
val = base64Encode(val)
|
val = getPlatform().base64Encode(val)
|
||||||
} else if (typeof val.toJSON === 'function') {
|
} else if (typeof val.toJSON === 'function') {
|
||||||
val = val.toJSON(true)
|
val = val.toJSON(true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
import { MtUnsupportedError } from '../../../types/errors.js'
|
|
||||||
import { ITelegramStorageProvider } from '../../storage/provider.js'
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
export const _defaultStorageFactory = (_name: string): ITelegramStorageProvider => {
|
|
||||||
throw new MtUnsupportedError('Please provide a storage explicitly (e.g. @mtcute/sqlite)')
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { IdbStorage } from '../../../storage/index.js'
|
|
||||||
import { MtUnsupportedError } from '../../../types/errors.js'
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
export const _defaultStorageFactory = (name: string) => {
|
|
||||||
if (typeof indexedDB !== 'undefined') {
|
|
||||||
return new IdbStorage(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new MtUnsupportedError('No storage available!')
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
import { base64DecodeToBuffer, base64Encode, TlBinaryReader, TlBinaryWriter, TlReaderMap } from '@mtcute/tl-runtime'
|
import { TlBinaryReader, TlBinaryWriter, TlReaderMap } from '@mtcute/tl-runtime'
|
||||||
|
|
||||||
|
import { getPlatform } from '../../platform.js'
|
||||||
import { MtArgumentError } from '../../types/index.js'
|
import { MtArgumentError } from '../../types/index.js'
|
||||||
import { BasicDcOption, DcOptions, parseBasicDcOption, serializeBasicDcOption } from '../../utils/dcs.js'
|
import { BasicDcOption, DcOptions, parseBasicDcOption, serializeBasicDcOption } from '../../utils/dcs.js'
|
||||||
import { CurrentUserInfo } from '../storage/service/current-user.js'
|
import { CurrentUserInfo } from '../storage/service/current-user.js'
|
||||||
|
@ -53,11 +54,11 @@ export function writeStringSession(data: StringSessionData): string {
|
||||||
|
|
||||||
writer.bytes(data.authKey)
|
writer.bytes(data.authKey)
|
||||||
|
|
||||||
return base64Encode(writer.result(), true)
|
return getPlatform().base64Encode(writer.result(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readStringSession(readerMap: TlReaderMap, data: string): StringSessionData {
|
export function readStringSession(readerMap: TlReaderMap, data: string): StringSessionData {
|
||||||
const buf = base64DecodeToBuffer(data, true)
|
const buf = getPlatform().base64Decode(data, true)
|
||||||
|
|
||||||
const version = buf[0]
|
const version = buf[0]
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import { hexDecodeToBuffer, hexEncode } from '@mtcute/tl-runtime'
|
import { getPlatform } from '../../platform.js'
|
||||||
|
|
||||||
import { decodeWaveform, encodeWaveform } from './voice-utils.js'
|
import { decodeWaveform, encodeWaveform } from './voice-utils.js'
|
||||||
|
|
||||||
|
const p = getPlatform()
|
||||||
|
|
||||||
describe('decodeWaveform', () => {
|
describe('decodeWaveform', () => {
|
||||||
it('should correctly decode telegram-encoded waveform', () => {
|
it('should correctly decode telegram-encoded waveform', () => {
|
||||||
expect(
|
expect(
|
||||||
decodeWaveform(
|
decodeWaveform(
|
||||||
hexDecodeToBuffer(
|
p.hexDecode(
|
||||||
'0000104210428c310821a51463cc39072184524a4aa9b51663acb5e69c7bef41' +
|
'0000104210428c310821a51463cc39072184524a4aa9b51663acb5e69c7bef41' +
|
||||||
'08618c514a39e7a494d65aadb5f75e8c31ce396badf7de9cf3debbf7feff0f',
|
'08618c514a39e7a494d65aadb5f75e8c31ce396badf7de9cf3debbf7feff0f',
|
||||||
),
|
),
|
||||||
|
@ -25,7 +26,7 @@ describe('decodeWaveform', () => {
|
||||||
describe('encodeWaveform', () => {
|
describe('encodeWaveform', () => {
|
||||||
it('should correctly decode telegram-encoded waveform', () => {
|
it('should correctly decode telegram-encoded waveform', () => {
|
||||||
expect(
|
expect(
|
||||||
hexEncode(
|
p.hexEncode(
|
||||||
encodeWaveform([
|
encodeWaveform([
|
||||||
0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 10,
|
0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 10,
|
||||||
10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 18,
|
10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 18,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { beforeExit } from '../../../utils/platform/exit-hook.js'
|
import { getPlatform } from '../../../platform.js'
|
||||||
import { ClientMessageHandler, SendFn, SomeWorker } from '../protocol.js'
|
import { ClientMessageHandler, SendFn, SomeWorker } from '../protocol.js'
|
||||||
|
|
||||||
export function connectToWorker(worker: SomeWorker, handler: ClientMessageHandler): [SendFn, () => void] {
|
export function connectToWorker(worker: SomeWorker, handler: ClientMessageHandler): [SendFn, () => void] {
|
||||||
|
@ -52,7 +52,7 @@ export function connectToWorker(worker: SomeWorker, handler: ClientMessageHandle
|
||||||
worker.port.close()
|
worker.port.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeExit(close)
|
getPlatform().beforeExit(close)
|
||||||
|
|
||||||
return [send, close]
|
return [send, close]
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,22 +3,19 @@ import { describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import { defaultTestCryptoProvider } from '@mtcute/test'
|
import { defaultTestCryptoProvider } from '@mtcute/test'
|
||||||
import {
|
import {
|
||||||
hexDecode,
|
|
||||||
hexDecodeToBuffer,
|
|
||||||
hexEncode,
|
|
||||||
TlBinaryReader,
|
TlBinaryReader,
|
||||||
TlReaderMap,
|
TlReaderMap,
|
||||||
utf8Decode,
|
|
||||||
utf8EncodeToBuffer,
|
|
||||||
} from '@mtcute/tl-runtime'
|
} from '@mtcute/tl-runtime'
|
||||||
|
|
||||||
|
import { getPlatform } from '../platform.js'
|
||||||
import { LogManager } from '../utils/index.js'
|
import { LogManager } from '../utils/index.js'
|
||||||
import { AuthKey } from './auth-key.js'
|
import { AuthKey } from './auth-key.js'
|
||||||
|
|
||||||
const authKey = new Uint8Array(256)
|
const authKey = new Uint8Array(256)
|
||||||
|
const p = getPlatform()
|
||||||
|
|
||||||
for (let i = 0; i < 256; i += 32) {
|
for (let i = 0; i < 256; i += 32) {
|
||||||
hexDecode(authKey.subarray(i, i + 32), '98cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4bf73c3622dec230e0')
|
authKey.subarray(i, i + 32).set(p.hexDecode('98cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4bf73c3622dec230e0'))
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('AuthKey', () => {
|
describe('AuthKey', () => {
|
||||||
|
@ -54,19 +51,19 @@ describe('AuthKey', () => {
|
||||||
it('should calculate derivatives', async () => {
|
it('should calculate derivatives', async () => {
|
||||||
const key = await create()
|
const key = await create()
|
||||||
|
|
||||||
expect(hexEncode(key.key)).toEqual(hexEncode(authKey))
|
expect(p.hexEncode(key.key)).toEqual(p.hexEncode(authKey))
|
||||||
expect(hexEncode(key.clientSalt)).toEqual('f73c3622dec230e098cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4b')
|
expect(p.hexEncode(key.clientSalt)).toEqual('f73c3622dec230e098cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4b')
|
||||||
expect(hexEncode(key.serverSalt)).toEqual('98cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4bf73c3622dec230e0')
|
expect(p.hexEncode(key.serverSalt)).toEqual('98cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4bf73c3622dec230e0')
|
||||||
expect(hexEncode(key.id)).toEqual('40fa5bb7cb56a895')
|
expect(p.hexEncode(key.id)).toEqual('40fa5bb7cb56a895')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should encrypt a message', async () => {
|
it('should encrypt a message', async () => {
|
||||||
const message = writeMessage(utf8EncodeToBuffer('hello, world!!!!'))
|
const message = writeMessage(p.utf8Encode('hello, world!!!!'))
|
||||||
|
|
||||||
const key = await create()
|
const key = await create()
|
||||||
const msg = key.encryptMessage(message, serverSalt, sessionId)
|
const msg = key.encryptMessage(message, serverSalt, sessionId)
|
||||||
|
|
||||||
expect(hexEncode(msg)).toEqual(
|
expect(p.hexEncode(msg)).toEqual(
|
||||||
'40fa5bb7cb56a895f6f5a88914892aadf87c68031cc953ba29d68e118021f329' +
|
'40fa5bb7cb56a895f6f5a88914892aadf87c68031cc953ba29d68e118021f329' +
|
||||||
'be386a620d49f3ad3a50c60dcef3733f214e8cefa3e403c11d193637d4971dc1' +
|
'be386a620d49f3ad3a50c60dcef3733f214e8cefa3e403c11d193637d4971dc1' +
|
||||||
'5db7f74b26fd16cb0e8fee30bf7e3f68858fe82927e2cd06',
|
'5db7f74b26fd16cb0e8fee30bf7e3f68858fe82927e2cd06',
|
||||||
|
@ -88,7 +85,7 @@ describe('AuthKey', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should decrypt a message', async () => {
|
it('should decrypt a message', async () => {
|
||||||
const message = hexDecodeToBuffer(
|
const message = p.hexDecode(
|
||||||
'40fa5bb7cb56a8950c394b884f1529efc42fea22d972fea650a714ce6d2d1bdb' +
|
'40fa5bb7cb56a8950c394b884f1529efc42fea22d972fea650a714ce6d2d1bdb' +
|
||||||
'3d98ff5929b8768c401771a69795f36a7e720dcafac2efbccd0ba368e8a7f48b' +
|
'3d98ff5929b8768c401771a69795f36a7e720dcafac2efbccd0ba368e8a7f48b' +
|
||||||
'07362cac1a32ffcabe188b51a36cc4d54e1d0633cf9eaf35',
|
'07362cac1a32ffcabe188b51a36cc4d54e1d0633cf9eaf35',
|
||||||
|
@ -98,11 +95,11 @@ describe('AuthKey', () => {
|
||||||
|
|
||||||
expect(decMsgId).toEqual(msgId)
|
expect(decMsgId).toEqual(msgId)
|
||||||
expect(decSeqNo).toEqual(seqNo)
|
expect(decSeqNo).toEqual(seqNo)
|
||||||
expect(utf8Decode(data.raw(16))).toEqual('hello, world!!!!')
|
expect(p.utf8Decode(data.raw(16))).toEqual('hello, world!!!!')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should decrypt a message with padding', async () => {
|
it('should decrypt a message with padding', async () => {
|
||||||
const message = hexDecodeToBuffer(
|
const message = p.hexDecode(
|
||||||
'40fa5bb7cb56a8950c394b884f1529efc42fea22d972fea650a714ce6d2d1bdb' +
|
'40fa5bb7cb56a8950c394b884f1529efc42fea22d972fea650a714ce6d2d1bdb' +
|
||||||
'3d98ff5929b8768c401771a69795f36a7e720dcafac2efbccd0ba368e8a7f48b' +
|
'3d98ff5929b8768c401771a69795f36a7e720dcafac2efbccd0ba368e8a7f48b' +
|
||||||
'07362cac1a32ffcabe188b51a36cc4d54e1d0633cf9eaf35' +
|
'07362cac1a32ffcabe188b51a36cc4d54e1d0633cf9eaf35' +
|
||||||
|
@ -113,11 +110,11 @@ describe('AuthKey', () => {
|
||||||
|
|
||||||
expect(decMsgId).toEqual(msgId)
|
expect(decMsgId).toEqual(msgId)
|
||||||
expect(decSeqNo).toEqual(seqNo)
|
expect(decSeqNo).toEqual(seqNo)
|
||||||
expect(utf8Decode(data.raw(16))).toEqual('hello, world!!!!')
|
expect(p.utf8Decode(data.raw(16))).toEqual('hello, world!!!!')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should ignore messages with invalid message key', async () => {
|
it('should ignore messages with invalid message key', async () => {
|
||||||
const message = hexDecodeToBuffer(
|
const message = p.hexDecode(
|
||||||
'40fa5bb7cb56a8950000000000000000000000000000000050a714ce6d2d1bdb' +
|
'40fa5bb7cb56a8950000000000000000000000000000000050a714ce6d2d1bdb' +
|
||||||
'3d98ff5929b8768c401771a69795f36a7e720dcafac2efbccd0ba368e8a7f48b' +
|
'3d98ff5929b8768c401771a69795f36a7e720dcafac2efbccd0ba368e8a7f48b' +
|
||||||
'07362cac1a32ffcabe188b51a36cc4d54e1d0633cf9eaf35',
|
'07362cac1a32ffcabe188b51a36cc4d54e1d0633cf9eaf35',
|
||||||
|
@ -127,7 +124,7 @@ describe('AuthKey', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should ignore messages with invalid session_id', async () => {
|
it('should ignore messages with invalid session_id', async () => {
|
||||||
const message = hexDecodeToBuffer(
|
const message = p.hexDecode(
|
||||||
'40fa5bb7cb56a895a986a7e97f4e90aa2769b5e702c6e86f5e1e82c6ff0c6829' +
|
'40fa5bb7cb56a895a986a7e97f4e90aa2769b5e702c6e86f5e1e82c6ff0c6829' +
|
||||||
'2521a2ba9704fa37fb341d895cf32662c6cf47ba31cbf27c30d5c03f6c2930f4' +
|
'2521a2ba9704fa37fb341d895cf32662c6cf47ba31cbf27c30d5c03f6c2930f4' +
|
||||||
'30fd8858b836b73fe32d4a95b8ebcdbc9ca8908f7964c40a',
|
'30fd8858b836b73fe32d4a95b8ebcdbc9ca8908f7964c40a',
|
||||||
|
@ -137,12 +134,12 @@ describe('AuthKey', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should ignore messages with invalid length', async () => {
|
it('should ignore messages with invalid length', async () => {
|
||||||
const messageTooLong = hexDecodeToBuffer(
|
const messageTooLong = p.hexDecode(
|
||||||
'40fa5bb7cb56a8950d19412233dd5d24be697c73274e08fbe515cf65e0c5f70c' +
|
'40fa5bb7cb56a8950d19412233dd5d24be697c73274e08fbe515cf65e0c5f70c' +
|
||||||
'ad75fd2badc18c9f999f287351144eeb1cfcaa9bea33ef5058999ad96a498306' +
|
'ad75fd2badc18c9f999f287351144eeb1cfcaa9bea33ef5058999ad96a498306' +
|
||||||
'08d2859425685a55b21fab413bfabc42ec5da283853b28c0',
|
'08d2859425685a55b21fab413bfabc42ec5da283853b28c0',
|
||||||
)
|
)
|
||||||
const messageUnaligned = hexDecodeToBuffer(
|
const messageUnaligned = p.hexDecode(
|
||||||
'40fa5bb7cb56a8957b4e4bec561eee4a5a1025bc8a35d3d0c79a3685d2b90ff0' +
|
'40fa5bb7cb56a8957b4e4bec561eee4a5a1025bc8a35d3d0c79a3685d2b90ff0' +
|
||||||
'5f638e9c42c9fd9448b0ce8e7d49e7ea1ce458e47b825b5c7fd8ddf5b4fded46' +
|
'5f638e9c42c9fd9448b0ce8e7d49e7ea1ce458e47b825b5c7fd8ddf5b4fded46' +
|
||||||
'2a4bcc02f3ff2e89de6764d6d219f575e457fdcf8c163cdf',
|
'2a4bcc02f3ff2e89de6764d6d219f575e457fdcf8c163cdf',
|
||||||
|
@ -155,7 +152,7 @@ describe('AuthKey', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should ignore messages with invalid padding', async () => {
|
it('should ignore messages with invalid padding', async () => {
|
||||||
const message = hexDecodeToBuffer(
|
const message = p.hexDecode(
|
||||||
'40fa5bb7cb56a895133671d1c637a9836e2c64b4d1a0521d8a25a6416fd4dc9e' +
|
'40fa5bb7cb56a895133671d1c637a9836e2c64b4d1a0521d8a25a6416fd4dc9e' +
|
||||||
'79f9478fb837703cc9efa0a19d12143c2a26e57cb4bc64d7bc972dd8f19c53c590cc258162f44afc',
|
'79f9478fb837703cc9efa0a19d12143c2a26e57cb4bc64d7bc972dd8f19c53c590cc258162f44afc',
|
||||||
)
|
)
|
||||||
|
|
|
@ -11,9 +11,7 @@ import { StorageManager, StorageManagerExtraOptions } from '../storage/storage.j
|
||||||
import { MustEqual } from '../types/index.js'
|
import { MustEqual } from '../types/index.js'
|
||||||
import {
|
import {
|
||||||
asyncResettable,
|
asyncResettable,
|
||||||
CryptoProviderFactory,
|
|
||||||
DcOptions,
|
DcOptions,
|
||||||
defaultCryptoProviderFactory,
|
|
||||||
defaultProductionDc,
|
defaultProductionDc,
|
||||||
defaultProductionIpv6Dc,
|
defaultProductionIpv6Dc,
|
||||||
defaultTestDc,
|
defaultTestDc,
|
||||||
|
@ -49,10 +47,10 @@ export interface MtClientOptions {
|
||||||
storageOptions?: StorageManagerExtraOptions
|
storageOptions?: StorageManagerExtraOptions
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cryptography provider factory to allow delegating
|
* Cryptography provider to allow delegating
|
||||||
* crypto to native addon, worker, etc.
|
* crypto to native addon, worker, etc.
|
||||||
*/
|
*/
|
||||||
crypto?: CryptoProviderFactory
|
crypto: ICryptoProvider
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to use IPv6 datacenters
|
* Whether to use IPv6 datacenters
|
||||||
|
@ -96,7 +94,7 @@ export interface MtClientOptions {
|
||||||
*
|
*
|
||||||
* @default platform-specific transport: WebSocket on the web, TCP in node
|
* @default platform-specific transport: WebSocket on the web, TCP in node
|
||||||
*/
|
*/
|
||||||
transport?: TransportFactory
|
transport: TransportFactory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reconnection strategy.
|
* Reconnection strategy.
|
||||||
|
@ -254,7 +252,7 @@ export class MtClient extends EventEmitter {
|
||||||
this.log.mgr.level = params.logLevel
|
this.log.mgr.level = params.logLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
this.crypto = (params.crypto ?? defaultCryptoProviderFactory)()
|
this.crypto = params.crypto
|
||||||
this._testMode = Boolean(params.testMode)
|
this._testMode = Boolean(params.testMode)
|
||||||
|
|
||||||
let dc = params.defaultDcs
|
let dc = params.defaultDcs
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { mtp, tl } from '@mtcute/tl'
|
import { mtp, tl } from '@mtcute/tl'
|
||||||
import { TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime'
|
import { TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime'
|
||||||
|
|
||||||
|
import { getPlatform } from '../platform.js'
|
||||||
import { StorageManager } from '../storage/storage.js'
|
import { StorageManager } from '../storage/storage.js'
|
||||||
import { MtArgumentError, MtcuteError, MtTimeoutError, MtUnsupportedError } from '../types/index.js'
|
import { MtArgumentError, MtcuteError, MtTimeoutError, MtUnsupportedError } from '../types/index.js'
|
||||||
import {
|
import {
|
||||||
|
@ -18,7 +19,7 @@ import { PersistentConnectionParams } from './persistent-connection.js'
|
||||||
import { defaultReconnectionStrategy, ReconnectionStrategy } from './reconnection.js'
|
import { defaultReconnectionStrategy, ReconnectionStrategy } from './reconnection.js'
|
||||||
import { ServerSaltManager } from './server-salt.js'
|
import { ServerSaltManager } from './server-salt.js'
|
||||||
import { SessionConnection, SessionConnectionParams } from './session-connection.js'
|
import { SessionConnection, SessionConnectionParams } from './session-connection.js'
|
||||||
import { defaultTransportFactory, TransportFactory } from './transports/index.js'
|
import { TransportFactory } from './transports/index.js'
|
||||||
|
|
||||||
export type ConnectionKind = 'main' | 'upload' | 'download' | 'downloadSmall'
|
export type ConnectionKind = 'main' | 'upload' | 'download' | 'downloadSmall'
|
||||||
|
|
||||||
|
@ -44,7 +45,7 @@ export interface NetworkManagerParams {
|
||||||
enableErrorReporting: boolean
|
enableErrorReporting: boolean
|
||||||
apiId: number
|
apiId: number
|
||||||
initConnectionOptions?: Partial<Omit<tl.RawInitConnectionRequest, 'apiId' | 'query'>>
|
initConnectionOptions?: Partial<Omit<tl.RawInitConnectionRequest, 'apiId' | 'query'>>
|
||||||
transport?: TransportFactory
|
transport: TransportFactory
|
||||||
reconnectionStrategy?: ReconnectionStrategy<PersistentConnectionParams>
|
reconnectionStrategy?: ReconnectionStrategy<PersistentConnectionParams>
|
||||||
floodSleepThreshold: number
|
floodSleepThreshold: number
|
||||||
maxRetryCount: number
|
maxRetryCount: number
|
||||||
|
@ -439,15 +440,7 @@ export class NetworkManager {
|
||||||
readonly params: NetworkManagerParams & NetworkManagerExtraParams,
|
readonly params: NetworkManagerParams & NetworkManagerExtraParams,
|
||||||
readonly config: ConfigManager,
|
readonly config: ConfigManager,
|
||||||
) {
|
) {
|
||||||
let deviceModel = 'mtcute on '
|
const deviceModel = `mtcute on ${getPlatform().getDeviceModel()}`
|
||||||
/* eslint-disable no-restricted-globals */
|
|
||||||
if (typeof process !== 'undefined' && typeof require !== 'undefined') {
|
|
||||||
const os = require('os') as typeof import('os')
|
|
||||||
deviceModel += `${os.type()} ${os.arch()} ${os.release()}`
|
|
||||||
/* eslint-enable no-restricted-globals */
|
|
||||||
} else if (typeof navigator !== 'undefined') {
|
|
||||||
deviceModel += navigator.userAgent
|
|
||||||
} else deviceModel += 'unknown'
|
|
||||||
|
|
||||||
this._initConnectionParams = {
|
this._initConnectionParams = {
|
||||||
_: 'initConnection',
|
_: 'initConnection',
|
||||||
|
@ -463,7 +456,7 @@ export class NetworkManager {
|
||||||
query: null as any,
|
query: null as any,
|
||||||
}
|
}
|
||||||
|
|
||||||
this._transportFactory = params.transport ?? defaultTransportFactory
|
this._transportFactory = params.transport
|
||||||
this._reconnectionStrategy = params.reconnectionStrategy ?? defaultReconnectionStrategy
|
this._reconnectionStrategy = params.reconnectionStrategy ?? defaultReconnectionStrategy
|
||||||
this._connectionCount = params.connectionCount ?? defaultConnectionCountDelegate
|
this._connectionCount = params.connectionCount ?? defaultConnectionCountDelegate
|
||||||
this._updateHandler = params.onUpdate
|
this._updateHandler = params.onUpdate
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { TlBinaryReader, TlBinaryWriter, TlReaderMap, TlSerializationCounter, Tl
|
||||||
|
|
||||||
import { MtArgumentError, MtcuteError, MtTimeoutError } from '../types/index.js'
|
import { MtArgumentError, MtcuteError, MtTimeoutError } from '../types/index.js'
|
||||||
import { createAesIgeForMessageOld } from '../utils/crypto/mtproto.js'
|
import { createAesIgeForMessageOld } from '../utils/crypto/mtproto.js'
|
||||||
|
import { reportUnknownError } from '../utils/error-reporting.js'
|
||||||
import {
|
import {
|
||||||
concatBuffers,
|
concatBuffers,
|
||||||
ControllablePromise,
|
ControllablePromise,
|
||||||
|
@ -17,7 +18,6 @@ import {
|
||||||
randomLong,
|
randomLong,
|
||||||
removeFromLongArray,
|
removeFromLongArray,
|
||||||
} from '../utils/index.js'
|
} from '../utils/index.js'
|
||||||
import { reportUnknownError } from '../utils/platform/error-reporting.js'
|
|
||||||
import { doAuthorization } from './authorization.js'
|
import { doAuthorization } from './authorization.js'
|
||||||
import { MtprotoSession, PendingMessage, PendingRpc } from './mtproto-session.js'
|
import { MtprotoSession, PendingMessage, PendingRpc } from './mtproto-session.js'
|
||||||
import { PersistentConnection, PersistentConnectionParams } from './persistent-connection.js'
|
import { PersistentConnection, PersistentConnectionParams } from './persistent-connection.js'
|
||||||
|
|
|
@ -1,12 +1,5 @@
|
||||||
import { TransportFactory } from './abstract.js'
|
|
||||||
|
|
||||||
export * from './abstract.js'
|
export * from './abstract.js'
|
||||||
export * from './intermediate.js'
|
export * from './intermediate.js'
|
||||||
export * from './obfuscated.js'
|
export * from './obfuscated.js'
|
||||||
export * from './streamed.js'
|
export * from './streamed.js'
|
||||||
export * from './wrapped.js'
|
export * from './wrapped.js'
|
||||||
|
|
||||||
import { _defaultTransportFactory } from '../../utils/platform/transport.js'
|
|
||||||
|
|
||||||
/** Platform-defined default transport factory */
|
|
||||||
export const defaultTransportFactory: TransportFactory = _defaultTransportFactory
|
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import { defaultTestCryptoProvider, useFakeMathRandom } from '@mtcute/test'
|
import { defaultTestCryptoProvider, useFakeMathRandom } from '@mtcute/test'
|
||||||
import { hexDecodeToBuffer, hexEncode } from '@mtcute/tl-runtime'
|
|
||||||
|
|
||||||
import { IntermediatePacketCodec, PaddedIntermediatePacketCodec, TransportError } from '../../index.js'
|
import { IntermediatePacketCodec, PaddedIntermediatePacketCodec, TransportError } from '../../index.js'
|
||||||
|
import { getPlatform } from '../../platform.js'
|
||||||
|
|
||||||
|
const p = getPlatform()
|
||||||
|
|
||||||
describe('IntermediatePacketCodec', () => {
|
describe('IntermediatePacketCodec', () => {
|
||||||
it('should return correct tag', () => {
|
it('should return correct tag', () => {
|
||||||
expect(hexEncode(new IntermediatePacketCodec().tag())).eq('eeeeeeee')
|
expect(p.hexEncode(new IntermediatePacketCodec().tag())).eq('eeeeeeee')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should correctly parse immediate framing', () =>
|
it('should correctly parse immediate framing', () =>
|
||||||
|
@ -17,7 +19,7 @@ describe('IntermediatePacketCodec', () => {
|
||||||
expect([...data]).eql([5, 1, 2, 3, 4])
|
expect([...data]).eql([5, 1, 2, 3, 4])
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
codec.feed(hexDecodeToBuffer('050000000501020304'))
|
codec.feed(p.hexDecode('050000000501020304'))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
it('should correctly parse incomplete framing', () =>
|
it('should correctly parse incomplete framing', () =>
|
||||||
|
@ -27,8 +29,8 @@ describe('IntermediatePacketCodec', () => {
|
||||||
expect([...data]).eql([5, 1, 2, 3, 4])
|
expect([...data]).eql([5, 1, 2, 3, 4])
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
codec.feed(hexDecodeToBuffer('050000000501'))
|
codec.feed(p.hexDecode('050000000501'))
|
||||||
codec.feed(hexDecodeToBuffer('020304'))
|
codec.feed(p.hexDecode('020304'))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
it('should correctly parse multiple streamed packets', () =>
|
it('should correctly parse multiple streamed packets', () =>
|
||||||
|
@ -46,9 +48,9 @@ describe('IntermediatePacketCodec', () => {
|
||||||
done()
|
done()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
codec.feed(hexDecodeToBuffer('050000000501'))
|
codec.feed(p.hexDecode('050000000501'))
|
||||||
codec.feed(hexDecodeToBuffer('020304050000'))
|
codec.feed(p.hexDecode('020304050000'))
|
||||||
codec.feed(hexDecodeToBuffer('000301020301'))
|
codec.feed(p.hexDecode('000301020301'))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
it('should correctly parse transport errors', () =>
|
it('should correctly parse transport errors', () =>
|
||||||
|
@ -61,7 +63,7 @@ describe('IntermediatePacketCodec', () => {
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
|
|
||||||
codec.feed(hexDecodeToBuffer('040000006cfeffff'))
|
codec.feed(p.hexDecode('040000006cfeffff'))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
it('should reset when called reset()', () =>
|
it('should reset when called reset()', () =>
|
||||||
|
@ -73,15 +75,15 @@ describe('IntermediatePacketCodec', () => {
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
|
|
||||||
codec.feed(hexDecodeToBuffer('ff0000001234567812345678'))
|
codec.feed(p.hexDecode('ff0000001234567812345678'))
|
||||||
codec.reset()
|
codec.reset()
|
||||||
codec.feed(hexDecodeToBuffer('050000000102030405'))
|
codec.feed(p.hexDecode('050000000102030405'))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
it('should correctly frame packets', () => {
|
it('should correctly frame packets', () => {
|
||||||
const data = hexDecodeToBuffer('6cfeffff')
|
const data = p.hexDecode('6cfeffff')
|
||||||
|
|
||||||
expect(hexEncode(new IntermediatePacketCodec().encode(data))).toEqual('040000006cfeffff')
|
expect(p.hexEncode(new IntermediatePacketCodec().encode(data))).toEqual('040000006cfeffff')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -96,12 +98,12 @@ describe('PaddedIntermediatePacketCodec', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should return correct tag', async () => {
|
it('should return correct tag', async () => {
|
||||||
expect(hexEncode((await create()).tag())).eq('dddddddd')
|
expect(p.hexEncode((await create()).tag())).eq('dddddddd')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should correctly frame packets', async () => {
|
it('should correctly frame packets', async () => {
|
||||||
const data = hexDecodeToBuffer('6cfeffff')
|
const data = p.hexDecode('6cfeffff')
|
||||||
|
|
||||||
expect(hexEncode((await create()).encode(data))).toEqual('0a0000006cfeffff29afd26df40f')
|
expect(p.hexEncode((await create()).encode(data))).toEqual('0a0000006cfeffff29afd26df40f')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,10 +2,13 @@ import { describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import { defaultTestCryptoProvider, u8HexDecode } from '@mtcute/test'
|
import { defaultTestCryptoProvider, u8HexDecode } from '@mtcute/test'
|
||||||
|
|
||||||
import { hexDecodeToBuffer, hexEncode, LogManager } from '../../utils/index.js'
|
import { getPlatform } from '../../platform.js'
|
||||||
|
import { LogManager } from '../../utils/index.js'
|
||||||
import { IntermediatePacketCodec } from './intermediate.js'
|
import { IntermediatePacketCodec } from './intermediate.js'
|
||||||
import { MtProxyInfo, ObfuscatedPacketCodec } from './obfuscated.js'
|
import { MtProxyInfo, ObfuscatedPacketCodec } from './obfuscated.js'
|
||||||
|
|
||||||
|
const p = getPlatform()
|
||||||
|
|
||||||
describe('ObfuscatedPacketCodec', () => {
|
describe('ObfuscatedPacketCodec', () => {
|
||||||
const create = async (randomSource?: string, proxy?: MtProxyInfo) => {
|
const create = async (randomSource?: string, proxy?: MtProxyInfo) => {
|
||||||
const codec = new ObfuscatedPacketCodec(new IntermediatePacketCodec(), proxy)
|
const codec = new ObfuscatedPacketCodec(new IntermediatePacketCodec(), proxy)
|
||||||
|
@ -22,7 +25,7 @@ describe('ObfuscatedPacketCodec', () => {
|
||||||
|
|
||||||
const tag = await codec.tag()
|
const tag = await codec.tag()
|
||||||
|
|
||||||
expect(hexEncode(tag)).toEqual(
|
expect(p.hexEncode(tag)).toEqual(
|
||||||
'ff'.repeat(56) + 'fce8ab2203db2bff', // encrypted part
|
'ff'.repeat(56) + 'fce8ab2203db2bff', // encrypted part
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -40,7 +43,7 @@ describe('ObfuscatedPacketCodec', () => {
|
||||||
|
|
||||||
const tag = await codec.tag()
|
const tag = await codec.tag()
|
||||||
|
|
||||||
expect(hexEncode(tag)).toEqual(
|
expect(p.hexEncode(tag)).toEqual(
|
||||||
'ff'.repeat(56) + 'ecec4cbda8bb188b', // encrypted part with dcId = 1
|
'ff'.repeat(56) + 'ecec4cbda8bb188b', // encrypted part with dcId = 1
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -57,7 +60,7 @@ describe('ObfuscatedPacketCodec', () => {
|
||||||
|
|
||||||
const tag = await codec.tag()
|
const tag = await codec.tag()
|
||||||
|
|
||||||
expect(hexEncode(tag)).toEqual(
|
expect(p.hexEncode(tag)).toEqual(
|
||||||
'ff'.repeat(56) + 'ecec4cbdb89c188b', // encrypted part with dcId = 10001
|
'ff'.repeat(56) + 'ecec4cbdb89c188b', // encrypted part with dcId = 10001
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -74,7 +77,7 @@ describe('ObfuscatedPacketCodec', () => {
|
||||||
|
|
||||||
const tag = await codec.tag()
|
const tag = await codec.tag()
|
||||||
|
|
||||||
expect(hexEncode(tag)).toEqual(
|
expect(p.hexEncode(tag)).toEqual(
|
||||||
'ff'.repeat(56) + 'ecec4cbd5644188b', // encrypted part with dcId = -1
|
'ff'.repeat(56) + 'ecec4cbd5644188b', // encrypted part with dcId = -1
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -124,7 +127,7 @@ describe('ObfuscatedPacketCodec', () => {
|
||||||
it('should correctly create aes ctr for mtproxy', async () => {
|
it('should correctly create aes ctr for mtproxy', async () => {
|
||||||
const proxy: MtProxyInfo = {
|
const proxy: MtProxyInfo = {
|
||||||
dcId: 1,
|
dcId: 1,
|
||||||
secret: hexDecodeToBuffer('00112233445566778899aabbccddeeff'),
|
secret: p.hexDecode('00112233445566778899aabbccddeeff'),
|
||||||
test: true,
|
test: true,
|
||||||
media: false,
|
media: false,
|
||||||
}
|
}
|
||||||
|
@ -137,20 +140,20 @@ describe('ObfuscatedPacketCodec', () => {
|
||||||
expect(spyCreateAesCtr).toHaveBeenCalledTimes(2)
|
expect(spyCreateAesCtr).toHaveBeenCalledTimes(2)
|
||||||
expect(spyCreateAesCtr).toHaveBeenNthCalledWith(
|
expect(spyCreateAesCtr).toHaveBeenNthCalledWith(
|
||||||
1,
|
1,
|
||||||
hexDecodeToBuffer('dd03188944590983e28dad14d97d0952389d118af4ffcbdb28d56a6a612ef7a6'),
|
p.hexDecode('dd03188944590983e28dad14d97d0952389d118af4ffcbdb28d56a6a612ef7a6'),
|
||||||
u8HexDecode('936b33fa7f97bae025102532233abb26'),
|
u8HexDecode('936b33fa7f97bae025102532233abb26'),
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
expect(spyCreateAesCtr).toHaveBeenNthCalledWith(
|
expect(spyCreateAesCtr).toHaveBeenNthCalledWith(
|
||||||
2,
|
2,
|
||||||
hexDecodeToBuffer('413b8e08021fbb08a2962b6d7187194fe46565c6b329d3bbdfcffd4870c16119'),
|
p.hexDecode('413b8e08021fbb08a2962b6d7187194fe46565c6b329d3bbdfcffd4870c16119'),
|
||||||
u8HexDecode('db6aeee6883f45f95def566dadb4b610'),
|
u8HexDecode('db6aeee6883f45f95def566dadb4b610'),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should correctly encrypt the underlying codec', async () => {
|
it('should correctly encrypt the underlying codec', async () => {
|
||||||
const data = hexDecodeToBuffer('6cfeffff')
|
const data = p.hexDecode('6cfeffff')
|
||||||
const msg1 = 'a1020630a410e940'
|
const msg1 = 'a1020630a410e940'
|
||||||
const msg2 = 'f53ff53f371db495'
|
const msg2 = 'f53ff53f371db495'
|
||||||
|
|
||||||
|
@ -158,8 +161,8 @@ describe('ObfuscatedPacketCodec', () => {
|
||||||
|
|
||||||
await codec.tag()
|
await codec.tag()
|
||||||
|
|
||||||
expect(hexEncode(await codec.encode(data))).toEqual(msg1)
|
expect(p.hexEncode(await codec.encode(data))).toEqual(msg1)
|
||||||
expect(hexEncode(await codec.encode(data))).toEqual(msg2)
|
expect(p.hexEncode(await codec.encode(data))).toEqual(msg2)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should correctly decrypt the underlying codec', async () => {
|
it('should correctly decrypt the underlying codec', async () => {
|
||||||
|
@ -176,8 +179,8 @@ describe('ObfuscatedPacketCodec', () => {
|
||||||
log.push(e.toString())
|
log.push(e.toString())
|
||||||
})
|
})
|
||||||
|
|
||||||
codec.feed(hexDecodeToBuffer(msg1))
|
codec.feed(p.hexDecode(msg1))
|
||||||
codec.feed(hexDecodeToBuffer(msg2))
|
codec.feed(p.hexDecode(msg2))
|
||||||
|
|
||||||
await vi.waitFor(() => expect(log).toEqual(['Error: Transport error: 404', 'Error: Transport error: 404']))
|
await vi.waitFor(() => expect(log).toEqual(['Error: Transport error: 404', 'Error: Transport error: 404']))
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,10 +2,13 @@ import { describe, expect, it, Mock, MockedObject, vi } from 'vitest'
|
||||||
|
|
||||||
import { defaultTestCryptoProvider, u8HexDecode } from '@mtcute/test'
|
import { defaultTestCryptoProvider, u8HexDecode } from '@mtcute/test'
|
||||||
|
|
||||||
import { defaultProductionDc, hexDecodeToBuffer, LogManager } from '../../utils/index.js'
|
import { getPlatform } from '../../platform.js'
|
||||||
|
import { defaultProductionDc, LogManager } from '../../utils/index.js'
|
||||||
import { TransportState } from './abstract.js'
|
import { TransportState } from './abstract.js'
|
||||||
import { WebSocketTransport } from './websocket.js'
|
import { WebSocketTransport } from './websocket.js'
|
||||||
|
|
||||||
|
const p = getPlatform()
|
||||||
|
|
||||||
describe('WebSocketTransport', () => {
|
describe('WebSocketTransport', () => {
|
||||||
const create = async () => {
|
const create = async () => {
|
||||||
const fakeWs = vi.fn().mockImplementation(() => ({
|
const fakeWs = vi.fn().mockImplementation(() => ({
|
||||||
|
@ -74,9 +77,9 @@ describe('WebSocketTransport', () => {
|
||||||
const socket = getLastSocket(ws)
|
const socket = getLastSocket(ws)
|
||||||
await vi.waitFor(() => expect(t.state()).toEqual(TransportState.Ready))
|
await vi.waitFor(() => expect(t.state()).toEqual(TransportState.Ready))
|
||||||
|
|
||||||
await t.send(hexDecodeToBuffer('00010203040506070809'))
|
await t.send(p.hexDecode('00010203040506070809'))
|
||||||
|
|
||||||
expect(socket.send).toHaveBeenCalledWith(hexDecodeToBuffer('af020630c8ef14bcf53af33853ea'))
|
expect(socket.send).toHaveBeenCalledWith(p.hexDecode('af020630c8ef14bcf53af33853ea'))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should correctly close', async () => {
|
it('should correctly close', async () => {
|
||||||
|
@ -101,7 +104,7 @@ describe('WebSocketTransport', () => {
|
||||||
const socket = getLastSocket(ws)
|
const socket = getLastSocket(ws)
|
||||||
await vi.waitFor(() => expect(t.state()).toEqual(TransportState.Ready))
|
await vi.waitFor(() => expect(t.state()).toEqual(TransportState.Ready))
|
||||||
|
|
||||||
const data = hexDecodeToBuffer('00010203040506070809')
|
const data = p.hexDecode('00010203040506070809')
|
||||||
const message = new MessageEvent('message', { data })
|
const message = new MessageEvent('message', { data })
|
||||||
|
|
||||||
const onMessageCall = socket.addEventListener.mock.calls.find(([event]) => event === 'message') as unknown as [
|
const onMessageCall = socket.addEventListener.mock.calls.find(([event]) => event === 'message') as unknown as [
|
||||||
|
|
34
packages/core/src/platform.ts
Normal file
34
packages/core/src/platform.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { ITlPlatform, TlBinaryReader, TlBinaryWriter } from '@mtcute/tl-runtime'
|
||||||
|
|
||||||
|
import { MtUnsupportedError } from './types/errors.js'
|
||||||
|
|
||||||
|
export interface ICorePlatform extends ITlPlatform {
|
||||||
|
beforeExit(fn: () => void): () => void
|
||||||
|
log(color: number, level: number, tag: string, fmt: string, args: unknown[]): void
|
||||||
|
getDefaultLogLevel(): number | null
|
||||||
|
getDeviceModel(): string
|
||||||
|
}
|
||||||
|
|
||||||
|
let _platform: ICorePlatform | null = null
|
||||||
|
|
||||||
|
export function setPlatform(platform: ICorePlatform): void {
|
||||||
|
if (_platform) {
|
||||||
|
if (_platform.constructor !== platform.constructor) {
|
||||||
|
throw new MtUnsupportedError('Platform may not be changed at runtime!')
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_platform = platform
|
||||||
|
TlBinaryReader.platform = platform
|
||||||
|
TlBinaryWriter.platform = platform
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPlatform(): ICorePlatform {
|
||||||
|
if (!_platform) {
|
||||||
|
throw new MtUnsupportedError('Platform is not set! Have you instantiated the client?')
|
||||||
|
}
|
||||||
|
|
||||||
|
return _platform
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
export * from './driver.js'
|
export * from './driver.js'
|
||||||
|
export * from './memory/index.js'
|
||||||
export * from './provider.js'
|
export * from './provider.js'
|
||||||
export * from './providers/idb/index.js'
|
|
||||||
export * from './providers/memory/index.js'
|
|
||||||
export * from './repository/index.js'
|
export * from './repository/index.js'
|
||||||
export * from './storage.js'
|
export * from './storage.js'
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { IStorageDriver } from '../../driver.js'
|
import { IStorageDriver } from '../driver.js'
|
||||||
|
|
||||||
export class MemoryStorageDriver implements IStorageDriver {
|
export class MemoryStorageDriver implements IStorageDriver {
|
||||||
readonly states: Map<string, object> = new Map()
|
readonly states: Map<string, object> = new Map()
|
|
@ -1,5 +1,5 @@
|
||||||
import { ITelegramStorageProvider } from '../../../highlevel/storage/provider.js'
|
import { ITelegramStorageProvider } from '../../highlevel/storage/provider.js'
|
||||||
import { IMtStorageProvider } from '../../provider.js'
|
import { IMtStorageProvider } from '../provider.js'
|
||||||
import { MemoryStorageDriver } from './driver.js'
|
import { MemoryStorageDriver } from './driver.js'
|
||||||
import { MemoryAuthKeysRepository } from './repository/auth-keys.js'
|
import { MemoryAuthKeysRepository } from './repository/auth-keys.js'
|
||||||
import { MemoryKeyValueRepository } from './repository/kv.js'
|
import { MemoryKeyValueRepository } from './repository/kv.js'
|
|
@ -1,4 +1,4 @@
|
||||||
import { IAuthKeysRepository } from '../../../repository/auth-keys.js'
|
import { IAuthKeysRepository } from '../../repository/auth-keys.js'
|
||||||
import { MemoryStorageDriver } from '../driver.js'
|
import { MemoryStorageDriver } from '../driver.js'
|
||||||
|
|
||||||
interface AuthKeysState {
|
interface AuthKeysState {
|
|
@ -1,4 +1,4 @@
|
||||||
import { IKeyValueRepository } from '../../../repository/key-value.js'
|
import { IKeyValueRepository } from '../../repository/key-value.js'
|
||||||
import { MemoryStorageDriver } from '../driver.js'
|
import { MemoryStorageDriver } from '../driver.js'
|
||||||
|
|
||||||
export class MemoryKeyValueRepository implements IKeyValueRepository {
|
export class MemoryKeyValueRepository implements IKeyValueRepository {
|
|
@ -1,4 +1,4 @@
|
||||||
import { IPeersRepository } from '../../../../highlevel/storage/repository/peers.js'
|
import { IPeersRepository } from '../../../highlevel/storage/repository/peers.js'
|
||||||
import { MemoryStorageDriver } from '../driver.js'
|
import { MemoryStorageDriver } from '../driver.js'
|
||||||
|
|
||||||
interface PeersState {
|
interface PeersState {
|
|
@ -1,4 +1,4 @@
|
||||||
import { IReferenceMessagesRepository } from '../../../../highlevel/storage/repository/ref-messages.js'
|
import { IReferenceMessagesRepository } from '../../../highlevel/storage/repository/ref-messages.js'
|
||||||
import { MemoryStorageDriver } from '../driver.js'
|
import { MemoryStorageDriver } from '../driver.js'
|
||||||
|
|
||||||
interface RefMessagesState {
|
interface RefMessagesState {
|
|
@ -2,7 +2,7 @@ import { __tlReaderMap } from '@mtcute/tl/binary/reader.js'
|
||||||
import { __tlWriterMap } from '@mtcute/tl/binary/writer.js'
|
import { __tlWriterMap } from '@mtcute/tl/binary/writer.js'
|
||||||
|
|
||||||
import { LogManager } from '../../utils/logger.js'
|
import { LogManager } from '../../utils/logger.js'
|
||||||
import { MemoryStorageDriver } from '../providers/memory/driver.js'
|
import { MemoryStorageDriver } from '../memory/driver.js'
|
||||||
import { ServiceOptions } from './base.js'
|
import { ServiceOptions } from './base.js'
|
||||||
|
|
||||||
export function testServiceOptions(): ServiceOptions {
|
export function testServiceOptions(): ServiceOptions {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime'
|
import { TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime'
|
||||||
|
|
||||||
import { asyncResettable, beforeExit } from '../utils/index.js'
|
import { getPlatform } from '../platform.js'
|
||||||
|
import { asyncResettable } from '../utils/index.js'
|
||||||
import { Logger } from '../utils/logger.js'
|
import { Logger } from '../utils/logger.js'
|
||||||
import { IMtStorageProvider } from './provider.js'
|
import { IMtStorageProvider } from './provider.js'
|
||||||
import { AuthKeysService } from './service/auth-keys.js'
|
import { AuthKeysService } from './service/auth-keys.js'
|
||||||
|
@ -62,7 +63,7 @@ export class StorageManager {
|
||||||
this.driver.setup?.(this.log)
|
this.driver.setup?.(this.log)
|
||||||
|
|
||||||
if (this.options.cleanup ?? true) {
|
if (this.options.cleanup ?? true) {
|
||||||
this._cleanupRestore = beforeExit(() => {
|
this._cleanupRestore = getPlatform().beforeExit(() => {
|
||||||
this._destroy().catch((err) => this.log.error('cleanup error: %s', err))
|
this._destroy().catch((err) => this.log.error('cleanup error: %s', err))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import { defaultTestCryptoProvider } from '@mtcute/test'
|
import { defaultTestCryptoProvider } from '@mtcute/test'
|
||||||
import { hexDecodeToBuffer } from '@mtcute/tl-runtime'
|
|
||||||
|
|
||||||
|
import { getPlatform } from '../platform.js'
|
||||||
import {
|
import {
|
||||||
bigIntBitLength,
|
bigIntBitLength,
|
||||||
bigIntGcd,
|
bigIntGcd,
|
||||||
|
@ -16,6 +16,8 @@ import {
|
||||||
twoMultiplicity,
|
twoMultiplicity,
|
||||||
} from './index.js'
|
} from './index.js'
|
||||||
|
|
||||||
|
const p = getPlatform()
|
||||||
|
|
||||||
describe('bigIntBitLength', () => {
|
describe('bigIntBitLength', () => {
|
||||||
it('should correctly calculate bit length', () => {
|
it('should correctly calculate bit length', () => {
|
||||||
expect(bigIntBitLength(0n)).eq(0)
|
expect(bigIntBitLength(0n)).eq(0)
|
||||||
|
@ -33,7 +35,7 @@ describe('bigIntToBuffer', () => {
|
||||||
expect([...bigIntToBuffer(BigInt('10495708'), 8, false)]).eql([0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x26, 0xdc])
|
expect([...bigIntToBuffer(BigInt('10495708'), 8, false)]).eql([0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x26, 0xdc])
|
||||||
expect([...bigIntToBuffer(BigInt('3038102549'), 4, false)]).eql([0xb5, 0x15, 0xc4, 0x15])
|
expect([...bigIntToBuffer(BigInt('3038102549'), 4, false)]).eql([0xb5, 0x15, 0xc4, 0x15])
|
||||||
expect([...bigIntToBuffer(BigInt('9341376580368336208'), 8, false)]).eql([
|
expect([...bigIntToBuffer(BigInt('9341376580368336208'), 8, false)]).eql([
|
||||||
...hexDecodeToBuffer('81A33C81D2020550'),
|
...p.hexDecode('81A33C81D2020550'),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -43,12 +45,12 @@ describe('bigIntToBuffer', () => {
|
||||||
expect([...bigIntToBuffer(BigInt('10495708'), 8, true)]).eql([0xdc, 0x26, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00])
|
expect([...bigIntToBuffer(BigInt('10495708'), 8, true)]).eql([0xdc, 0x26, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00])
|
||||||
expect([...bigIntToBuffer(BigInt('3038102549'), 4, true)]).eql([0x15, 0xc4, 0x15, 0xb5])
|
expect([...bigIntToBuffer(BigInt('3038102549'), 4, true)]).eql([0x15, 0xc4, 0x15, 0xb5])
|
||||||
expect([...bigIntToBuffer(BigInt('9341376580368336208'), 8, true)]).eql([
|
expect([...bigIntToBuffer(BigInt('9341376580368336208'), 8, true)]).eql([
|
||||||
...hexDecodeToBuffer('81A33C81D2020550').reverse(),
|
...p.hexDecode('81A33C81D2020550').reverse(),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle large integers', () => {
|
it('should handle large integers', () => {
|
||||||
const buf = hexDecodeToBuffer(
|
const buf = p.hexDecode(
|
||||||
'1a981ce8bf86bf4a1bd79c2ef829914172f8d0e54cb7ad807552d56977e1c946872e2c7bd77052be30e7e9a7a35c4feff848a25759f5f2f5b0e96538',
|
'1a981ce8bf86bf4a1bd79c2ef829914172f8d0e54cb7ad807552d56977e1c946872e2c7bd77052be30e7e9a7a35c4feff848a25759f5f2f5b0e96538',
|
||||||
)
|
)
|
||||||
const num = BigInt(
|
const num = BigInt(
|
||||||
|
@ -74,7 +76,7 @@ describe('bufferToBigInt', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle large integers', () => {
|
it('should handle large integers', () => {
|
||||||
const buf = hexDecodeToBuffer(
|
const buf = p.hexDecode(
|
||||||
'1a981ce8bf86bf4a1bd79c2ef829914172f8d0e54cb7ad807552d56977e1c946872e2c7bd77052be30e7e9a7a35c4feff848a25759f5f2f5b0e96538',
|
'1a981ce8bf86bf4a1bd79c2ef829914172f8d0e54cb7ad807552d56977e1c946872e2c7bd77052be30e7e9a7a35c4feff848a25759f5f2f5b0e96538',
|
||||||
)
|
)
|
||||||
const num = BigInt(
|
const num = BigInt(
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
// all available libraries either suck or are extremely large for the use case, so i made my own~
|
// all available libraries either suck or are extremely large for the use case, so i made my own~
|
||||||
|
|
||||||
import { base64DecodeToBuffer, hexEncode } from '@mtcute/tl-runtime'
|
import { getPlatform } from '../../platform.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a single PEM block to buffer.
|
* Parses a single PEM block to buffer.
|
||||||
* In fact just strips begin/end tags and parses the rest as Base64
|
* In fact just strips begin/end tags and parses the rest as Base64
|
||||||
*/
|
*/
|
||||||
export function parsePemContents(pem: string): Uint8Array {
|
export function parsePemContents(pem: string): Uint8Array {
|
||||||
return base64DecodeToBuffer(pem.replace(/^-----(BEGIN|END)( RSA)? PUBLIC KEY-----$|\n/gm, ''))
|
return getPlatform().base64Decode(pem.replace(/^-----(BEGIN|END)( RSA)? PUBLIC KEY-----$|\n/gm, ''))
|
||||||
}
|
}
|
||||||
|
|
||||||
// based on https://git.coolaj86.com/coolaj86/asn1-parser.js/src/branch/master/asn1-parser.js
|
// based on https://git.coolaj86.com/coolaj86/asn1-parser.js/src/branch/master/asn1-parser.js
|
||||||
|
@ -66,7 +66,7 @@ export function parseAsn1(data: Uint8Array): Asn1Object {
|
||||||
if (0x80 & asn1.length) {
|
if (0x80 & asn1.length) {
|
||||||
asn1.lengthSize = 0x7f & asn1.length
|
asn1.lengthSize = 0x7f & asn1.length
|
||||||
// I think that buf->hex->int solves the problem of Endianness... not sure
|
// I think that buf->hex->int solves the problem of Endianness... not sure
|
||||||
asn1.length = parseInt(hexEncode(buf.subarray(index, index + asn1.lengthSize)), 16)
|
asn1.length = parseInt(getPlatform().hexEncode(buf.subarray(index, index + asn1.lengthSize)), 16)
|
||||||
index += asn1.lengthSize
|
index += asn1.lengthSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,5 +55,3 @@ export abstract class BaseCryptoProvider {
|
||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CryptoProviderFactory = () => ICryptoProvider
|
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import { defaultCryptoProvider } from '@mtcute/test'
|
||||||
|
|
||||||
import { bigIntToBuffer, bufferToBigInt } from '../bigint-utils.js'
|
import { bigIntToBuffer, bufferToBigInt } from '../bigint-utils.js'
|
||||||
import { factorizePQSync } from './factorization.js'
|
import { factorizePQSync } from './factorization.js'
|
||||||
import { defaultCryptoProviderFactory } from './index.js'
|
|
||||||
|
|
||||||
describe('prime factorization', () => {
|
describe('prime factorization', () => {
|
||||||
const testFactorization = (pq: bigint, p: bigint, q: bigint) => {
|
const testFactorization = (pq: bigint, p: bigint, q: bigint) => {
|
||||||
const [p_, q_] = factorizePQSync(defaultCryptoProviderFactory(), bigIntToBuffer(pq))
|
const [p_, q_] = factorizePQSync(defaultCryptoProvider, bigIntToBuffer(pq))
|
||||||
expect(bufferToBigInt(p_)).toBe(p)
|
expect(bufferToBigInt(p_)).toBe(p)
|
||||||
expect(bufferToBigInt(q_)).toBe(q)
|
expect(bufferToBigInt(q_)).toBe(q)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,4 @@ export * from './miller-rabin.js'
|
||||||
export * from './mtproto.js'
|
export * from './mtproto.js'
|
||||||
export * from './password.js'
|
export * from './password.js'
|
||||||
export * from './utils.js'
|
export * from './utils.js'
|
||||||
|
export * from './wasm.js'
|
||||||
import { _defaultCryptoProviderFactory } from '../platform/crypto.js'
|
|
||||||
import { CryptoProviderFactory } from './abstract.js'
|
|
||||||
|
|
||||||
export const defaultCryptoProviderFactory: CryptoProviderFactory = _defaultCryptoProviderFactory
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import { findKeyByFingerprints, parsePublicKey } from '../index.js'
|
import { defaultCryptoProvider } from '@mtcute/test'
|
||||||
import { NodeCryptoProvider } from './node.js'
|
|
||||||
|
|
||||||
const crypto = new NodeCryptoProvider()
|
import { findKeyByFingerprints, parsePublicKey } from '../index.js'
|
||||||
|
|
||||||
|
const crypto = defaultCryptoProvider
|
||||||
|
|
||||||
describe('parsePublicKey', () => {
|
describe('parsePublicKey', () => {
|
||||||
it('should parse telegram public keys', () => {
|
it('should parse telegram public keys', () => {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import Long from 'long'
|
import Long from 'long'
|
||||||
|
|
||||||
import { __publicKeyIndex as keysIndex, TlPublicKey } from '@mtcute/tl/binary/rsa-keys.js'
|
import { __publicKeyIndex as keysIndex, TlPublicKey } from '@mtcute/tl/binary/rsa-keys.js'
|
||||||
import { hexEncode, TlBinaryWriter } from '@mtcute/tl-runtime'
|
import { TlBinaryWriter } from '@mtcute/tl-runtime'
|
||||||
|
|
||||||
|
import { getPlatform } from '../../platform.js'
|
||||||
import { parseAsn1, parsePemContents } from '../binary/asn1-parser.js'
|
import { parseAsn1, parsePemContents } from '../binary/asn1-parser.js'
|
||||||
import { ICryptoProvider } from './abstract.js'
|
import { ICryptoProvider } from './abstract.js'
|
||||||
|
|
||||||
|
@ -25,13 +26,15 @@ export function parsePublicKey(crypto: ICryptoProvider, key: string, old = false
|
||||||
writer.bytes(modulus)
|
writer.bytes(modulus)
|
||||||
writer.bytes(exponent)
|
writer.bytes(exponent)
|
||||||
|
|
||||||
|
const platform = getPlatform()
|
||||||
|
|
||||||
const data = writer.result()
|
const data = writer.result()
|
||||||
const sha = crypto.sha1(data)
|
const sha = crypto.sha1(data)
|
||||||
const fp = hexEncode(sha.slice(-8).reverse())
|
const fp = platform.hexEncode(sha.slice(-8).reverse())
|
||||||
|
|
||||||
return {
|
return {
|
||||||
modulus: hexEncode(modulus),
|
modulus: platform.hexEncode(modulus),
|
||||||
exponent: hexEncode(exponent),
|
exponent: platform.hexEncode(exponent),
|
||||||
fingerprint: fp,
|
fingerprint: fp,
|
||||||
old,
|
old,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import { defaultCryptoProviderFactory } from './index.js'
|
import { defaultCryptoProvider } from '@mtcute/test'
|
||||||
|
|
||||||
import { millerRabin } from './miller-rabin.js'
|
import { millerRabin } from './miller-rabin.js'
|
||||||
|
|
||||||
describe(
|
describe(
|
||||||
'miller-rabin test',
|
'miller-rabin test',
|
||||||
function () {
|
function () {
|
||||||
// miller-rabin factorization relies on RNG, so we should use a real random number generator
|
// miller-rabin factorization relies on RNG, so we should use a real random number generator
|
||||||
const c = defaultCryptoProviderFactory()
|
const c = defaultCryptoProvider
|
||||||
|
|
||||||
const testMillerRabin = (n: number | string | bigint, isPrime: boolean) => {
|
const testMillerRabin = (n: number | string | bigint, isPrime: boolean) => {
|
||||||
expect(millerRabin(c, BigInt(n))).eq(isPrime)
|
expect(millerRabin(c, BigInt(n))).eq(isPrime)
|
||||||
|
|
|
@ -2,12 +2,15 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import { defaultTestCryptoProvider, u8HexDecode } from '@mtcute/test'
|
import { defaultTestCryptoProvider, u8HexDecode } from '@mtcute/test'
|
||||||
|
|
||||||
import { concatBuffers, hexDecodeToBuffer, hexEncode } from '../index.js'
|
import { getPlatform } from '../../platform.js'
|
||||||
|
import { concatBuffers } from '../index.js'
|
||||||
import { createAesIgeForMessage, createAesIgeForMessageOld, generateKeyAndIvFromNonce } from './mtproto.js'
|
import { createAesIgeForMessage, createAesIgeForMessageOld, generateKeyAndIvFromNonce } from './mtproto.js'
|
||||||
|
|
||||||
const authKeyChunk = hexDecodeToBuffer('98cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4bf73c3622dec230e0')
|
const p = getPlatform()
|
||||||
|
|
||||||
|
const authKeyChunk = p.hexDecode('98cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4bf73c3622dec230e0')
|
||||||
const authKey = concatBuffers(Array.from({ length: 8 }, () => authKeyChunk))
|
const authKey = concatBuffers(Array.from({ length: 8 }, () => authKeyChunk))
|
||||||
const messageKey = hexDecodeToBuffer('25d701f2a29205526757825a99eb2d32')
|
const messageKey = p.hexDecode('25d701f2a29205526757825a99eb2d32')
|
||||||
|
|
||||||
describe('mtproto 2.0', async () => {
|
describe('mtproto 2.0', async () => {
|
||||||
const crypto = await defaultTestCryptoProvider()
|
const crypto = await defaultTestCryptoProvider()
|
||||||
|
@ -18,10 +21,10 @@ describe('mtproto 2.0', async () => {
|
||||||
it('should correctly derive message key and iv for client', () => {
|
it('should correctly derive message key and iv for client', () => {
|
||||||
createAesIgeForMessage(crypto, authKey, messageKey, true)
|
createAesIgeForMessage(crypto, authKey, messageKey, true)
|
||||||
|
|
||||||
expect(hexEncode(createAesIgeSpy.mock.calls[0][0])).toEqual(
|
expect(p.hexEncode(createAesIgeSpy.mock.calls[0][0])).toEqual(
|
||||||
'af3f8e1ffa75f4c981eec33a3e5bbaa2ea48f9bb93e91597627eb1f67960a0c9',
|
'af3f8e1ffa75f4c981eec33a3e5bbaa2ea48f9bb93e91597627eb1f67960a0c9',
|
||||||
)
|
)
|
||||||
expect(hexEncode(createAesIgeSpy.mock.calls[0][1])).toEqual(
|
expect(p.hexEncode(createAesIgeSpy.mock.calls[0][1])).toEqual(
|
||||||
'9874d77f95155b35221bff94b7df4594c6996e2a62e44fcb7d93c8c4e41b79ee',
|
'9874d77f95155b35221bff94b7df4594c6996e2a62e44fcb7d93c8c4e41b79ee',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -29,10 +32,10 @@ describe('mtproto 2.0', async () => {
|
||||||
it('should correctly derive message key and iv for server', () => {
|
it('should correctly derive message key and iv for server', () => {
|
||||||
createAesIgeForMessage(crypto, authKey, messageKey, false)
|
createAesIgeForMessage(crypto, authKey, messageKey, false)
|
||||||
|
|
||||||
expect(hexEncode(createAesIgeSpy.mock.calls[0][0])).toEqual(
|
expect(p.hexEncode(createAesIgeSpy.mock.calls[0][0])).toEqual(
|
||||||
'd4b378e1e0525f10ff9d4c42807ccce5b30a033a8088c0b922b5259421751648',
|
'd4b378e1e0525f10ff9d4c42807ccce5b30a033a8088c0b922b5259421751648',
|
||||||
)
|
)
|
||||||
expect(hexEncode(createAesIgeSpy.mock.calls[0][1])).toEqual(
|
expect(p.hexEncode(createAesIgeSpy.mock.calls[0][1])).toEqual(
|
||||||
'4d7194f42f0135d2fd83050b403265b4c40ee3e9e9fba56f0f4d8ea6bcb121f5',
|
'4d7194f42f0135d2fd83050b403265b4c40ee3e9e9fba56f0f4d8ea6bcb121f5',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -47,10 +50,10 @@ describe('mtproto 1.0', async () => {
|
||||||
it('should correctly derive message key and iv for client', () => {
|
it('should correctly derive message key and iv for client', () => {
|
||||||
createAesIgeForMessageOld(crypto, authKey, messageKey, true)
|
createAesIgeForMessageOld(crypto, authKey, messageKey, true)
|
||||||
|
|
||||||
expect(hexEncode(createAesIgeSpy.mock.calls[0][0])).toEqual(
|
expect(p.hexEncode(createAesIgeSpy.mock.calls[0][0])).toEqual(
|
||||||
'1fc7b40b1d9ffbdaf4d652525a748864259698f89214abf27c0d36cb9d4cd5db',
|
'1fc7b40b1d9ffbdaf4d652525a748864259698f89214abf27c0d36cb9d4cd5db',
|
||||||
)
|
)
|
||||||
expect(hexEncode(createAesIgeSpy.mock.calls[0][1])).toEqual(
|
expect(p.hexEncode(createAesIgeSpy.mock.calls[0][1])).toEqual(
|
||||||
'7251fbda39ec5e6e089f15ded5963b03d6d8d0f7078898431fc7b40b1d9ffbda',
|
'7251fbda39ec5e6e089f15ded5963b03d6d8d0f7078898431fc7b40b1d9ffbda',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -58,10 +61,10 @@ describe('mtproto 1.0', async () => {
|
||||||
it('should correctly derive message key and iv for server', () => {
|
it('should correctly derive message key and iv for server', () => {
|
||||||
createAesIgeForMessageOld(crypto, authKey, messageKey, false)
|
createAesIgeForMessageOld(crypto, authKey, messageKey, false)
|
||||||
|
|
||||||
expect(hexEncode(createAesIgeSpy.mock.calls[0][0])).toEqual(
|
expect(p.hexEncode(createAesIgeSpy.mock.calls[0][0])).toEqual(
|
||||||
'af0e4e01318654be40ab42b125909d43b44bdeef571ff1a5dfb81474ae26d467',
|
'af0e4e01318654be40ab42b125909d43b44bdeef571ff1a5dfb81474ae26d467',
|
||||||
)
|
)
|
||||||
expect(hexEncode(createAesIgeSpy.mock.calls[0][1])).toEqual(
|
expect(p.hexEncode(createAesIgeSpy.mock.calls[0][1])).toEqual(
|
||||||
'15c9ba6021d2c5cf04f0842540ae216a970b4eac8f46ef01af0e4e01318654be',
|
'15c9ba6021d2c5cf04f0842540ae216a970b4eac8f46ef01af0e4e01318654be',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,17 +3,19 @@ import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import { defaultTestCryptoProvider } from '@mtcute/test'
|
import { defaultTestCryptoProvider } from '@mtcute/test'
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
import { hexDecodeToBuffer, hexEncode, utf8EncodeToBuffer } from '@mtcute/tl-runtime'
|
|
||||||
|
|
||||||
|
import { getPlatform } from '../../platform.js'
|
||||||
import { computeNewPasswordHash, computePasswordHash, computeSrpParams } from './index.js'
|
import { computeNewPasswordHash, computePasswordHash, computeSrpParams } from './index.js'
|
||||||
|
|
||||||
|
const p = getPlatform()
|
||||||
|
|
||||||
// a real-world request from an account with "qwe123" password
|
// a real-world request from an account with "qwe123" password
|
||||||
const fakeAlgo: tl.RawPasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow = {
|
const fakeAlgo: tl.RawPasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow = {
|
||||||
_: 'passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow',
|
_: 'passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow',
|
||||||
salt1: hexDecodeToBuffer('9b3accc457c0d5288e8cff31eb21094048bc11902f6614dbb9afb839ee7641c37619537d8ebe749e'),
|
salt1: p.hexDecode('9b3accc457c0d5288e8cff31eb21094048bc11902f6614dbb9afb839ee7641c37619537d8ebe749e'),
|
||||||
salt2: hexDecodeToBuffer('6c619bb0786dc4ed1bf211d23f6e4065'),
|
salt2: p.hexDecode('6c619bb0786dc4ed1bf211d23f6e4065'),
|
||||||
g: 3,
|
g: 3,
|
||||||
p: hexDecodeToBuffer(
|
p: p.hexDecode(
|
||||||
'c71caeb9c6b1c9048e6c522f70f13f73980d40238e3e21c14934d037563d930f' +
|
'c71caeb9c6b1c9048e6c522f70f13f73980d40238e3e21c14934d037563d930f' +
|
||||||
'48198a0aa7c14058229493d22530f4dbfa336f6e0ac925139543aed44cce7c37' +
|
'48198a0aa7c14058229493d22530f4dbfa336f6e0ac925139543aed44cce7c37' +
|
||||||
'20fd51f69458705ac68cd4fe6b6b13abdc9746512969328454f18faf8c595f64' +
|
'20fd51f69458705ac68cd4fe6b6b13abdc9746512969328454f18faf8c595f64' +
|
||||||
|
@ -30,7 +32,7 @@ const fakeRequest: tl.account.RawPassword = {
|
||||||
hasSecureValues: false,
|
hasSecureValues: false,
|
||||||
hasPassword: true,
|
hasPassword: true,
|
||||||
currentAlgo: fakeAlgo,
|
currentAlgo: fakeAlgo,
|
||||||
srpB: hexDecodeToBuffer(
|
srpB: p.hexDecode(
|
||||||
'1476a7b5991d7f028bbee33b3455cad3f2cd0eb3737409fcce92fa7d4cd5c733' +
|
'1476a7b5991d7f028bbee33b3455cad3f2cd0eb3737409fcce92fa7d4cd5c733' +
|
||||||
'ec6d2cb3454e587d4c17eda2fd7ef9a57327215f38292cc8bd5dc77d3e1d31cd' +
|
'ec6d2cb3454e587d4c17eda2fd7ef9a57327215f38292cc8bd5dc77d3e1d31cd' +
|
||||||
'dae2652f8347c4b0093f7c78242f70e6cc13137ee7acc257a49855a63113db8f' +
|
'dae2652f8347c4b0093f7c78242f70e6cc13137ee7acc257a49855a63113db8f' +
|
||||||
|
@ -43,10 +45,10 @@ const fakeRequest: tl.account.RawPassword = {
|
||||||
srpId: Long.fromBits(-2046015018, 875006452),
|
srpId: Long.fromBits(-2046015018, 875006452),
|
||||||
newAlgo: {
|
newAlgo: {
|
||||||
_: 'passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow',
|
_: 'passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow',
|
||||||
salt1: hexDecodeToBuffer('9b3accc457c0d528'),
|
salt1: p.hexDecode('9b3accc457c0d528'),
|
||||||
salt2: hexDecodeToBuffer('6c619bb0786dc4ed1bf211d23f6e4065'),
|
salt2: p.hexDecode('6c619bb0786dc4ed1bf211d23f6e4065'),
|
||||||
g: 3,
|
g: 3,
|
||||||
p: hexDecodeToBuffer(
|
p: p.hexDecode(
|
||||||
'c71caeb9c6b1c9048e6c522f70f13f73980d40238e3e21c14934d037563d930f' +
|
'c71caeb9c6b1c9048e6c522f70f13f73980d40238e3e21c14934d037563d930f' +
|
||||||
'48198a0aa7c14058229493d22530f4dbfa336f6e0ac925139543aed44cce7c37' +
|
'48198a0aa7c14058229493d22530f4dbfa336f6e0ac925139543aed44cce7c37' +
|
||||||
'20fd51f69458705ac68cd4fe6b6b13abdc9746512969328454f18faf8c595f64' +
|
'20fd51f69458705ac68cd4fe6b6b13abdc9746512969328454f18faf8c595f64' +
|
||||||
|
@ -59,7 +61,7 @@ const fakeRequest: tl.account.RawPassword = {
|
||||||
},
|
},
|
||||||
newSecureAlgo: {
|
newSecureAlgo: {
|
||||||
_: 'securePasswordKdfAlgoPBKDF2HMACSHA512iter100000',
|
_: 'securePasswordKdfAlgoPBKDF2HMACSHA512iter100000',
|
||||||
salt: hexDecodeToBuffer('fdd59abc0bffb24d'),
|
salt: p.hexDecode('fdd59abc0bffb24d'),
|
||||||
},
|
},
|
||||||
secureRandom: new Uint8Array(), // unused
|
secureRandom: new Uint8Array(), // unused
|
||||||
}
|
}
|
||||||
|
@ -68,16 +70,16 @@ const password = 'qwe123'
|
||||||
describe('SRP', () => {
|
describe('SRP', () => {
|
||||||
it('should correctly compute password hash as defined by MTProto', async () => {
|
it('should correctly compute password hash as defined by MTProto', async () => {
|
||||||
const crypto = await defaultTestCryptoProvider()
|
const crypto = await defaultTestCryptoProvider()
|
||||||
const hash = await computePasswordHash(crypto, utf8EncodeToBuffer(password), fakeAlgo.salt1, fakeAlgo.salt2)
|
const hash = await computePasswordHash(crypto, p.utf8Encode(password), fakeAlgo.salt1, fakeAlgo.salt2)
|
||||||
|
|
||||||
expect(hexEncode(hash)).toEqual('750f1fe282965e63ce17b98427b35549fb864465211840f6a7c1f2fb657cc33b')
|
expect(p.hexEncode(hash)).toEqual('750f1fe282965e63ce17b98427b35549fb864465211840f6a7c1f2fb657cc33b')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should correctly compute new password hash as defined by MTProto', async () => {
|
it('should correctly compute new password hash as defined by MTProto', async () => {
|
||||||
const crypto = await defaultTestCryptoProvider()
|
const crypto = await defaultTestCryptoProvider()
|
||||||
const hash = await computeNewPasswordHash(crypto, fakeAlgo, '123qwe')
|
const hash = await computeNewPasswordHash(crypto, fakeAlgo, '123qwe')
|
||||||
|
|
||||||
expect(hexEncode(hash)).toEqual(
|
expect(p.hexEncode(hash)).toEqual(
|
||||||
'2540539ceeffd4543cd845bf319b8392e6b17bf7cf26bafcf6282ce9ae795368' +
|
'2540539ceeffd4543cd845bf319b8392e6b17bf7cf26bafcf6282ce9ae795368' +
|
||||||
'4ff49469c2863b17e6d65ddb16ae6f60bc07cc254c00e5ba389292f6cea0b3aa' +
|
'4ff49469c2863b17e6d65ddb16ae6f60bc07cc254c00e5ba389292f6cea0b3aa' +
|
||||||
'c459d1d08984d65319df8c5d124042169bbe2ab8c0c93bc7178827f2ea84e7c3' +
|
'c459d1d08984d65319df8c5d124042169bbe2ab8c0c93bc7178827f2ea84e7c3' +
|
||||||
|
@ -94,7 +96,7 @@ describe('SRP', () => {
|
||||||
const params = await computeSrpParams(crypto, fakeRequest, password)
|
const params = await computeSrpParams(crypto, fakeRequest, password)
|
||||||
|
|
||||||
expect(params.srpId).toEqual(fakeRequest.srpId)
|
expect(params.srpId).toEqual(fakeRequest.srpId)
|
||||||
expect(hexEncode(params.A)).toEqual(
|
expect(p.hexEncode(params.A)).toEqual(
|
||||||
'363976f55edb57cc5cc0c4aaca9b7539eff98a43a93fa84be34860d18ac3a80f' +
|
'363976f55edb57cc5cc0c4aaca9b7539eff98a43a93fa84be34860d18ac3a80f' +
|
||||||
'ffd57c4617896ff667677d0552a079eb189d25d147ec96edd4495c946a18652d' +
|
'ffd57c4617896ff667677d0552a079eb189d25d147ec96edd4495c946a18652d' +
|
||||||
'31d78eede40a8b29da340c19b32ccac78f8482406e392102c03d850d1db87223' +
|
'31d78eede40a8b29da340c19b32ccac78f8482406e392102c03d850d1db87223' +
|
||||||
|
@ -104,6 +106,6 @@ describe('SRP', () => {
|
||||||
'4fa454aa69d9219d9c5fa3625f5c6f1ac03892a70aa17269c76cd9bf2949a961' +
|
'4fa454aa69d9219d9c5fa3625f5c6f1ac03892a70aa17269c76cd9bf2949a961' +
|
||||||
'fad2a71e5fa961824b32db037130c7e9aad4c1e9f02ebc5b832622f98b59597e',
|
'fad2a71e5fa961824b32db037130c7e9aad4c1e9f02ebc5b832622f98b59597e',
|
||||||
)
|
)
|
||||||
expect(hexEncode(params.M1)).toEqual('25a91b21c634ad670a144165a9829192d152e131a716f676abc48cd817f508c6')
|
expect(p.hexEncode(params.M1)).toEqual('25a91b21c634ad670a144165a9829192d152e131a716f676abc48cd817f508c6')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
import { utf8EncodeToBuffer } from '@mtcute/tl-runtime'
|
|
||||||
|
|
||||||
|
import { getPlatform } from '../../platform.js'
|
||||||
import { MtSecurityError, MtUnsupportedError } from '../../types/errors.js'
|
import { MtSecurityError, MtUnsupportedError } from '../../types/errors.js'
|
||||||
import { bigIntModPow, bigIntToBuffer, bufferToBigInt } from '../bigint-utils.js'
|
import { bigIntModPow, bigIntToBuffer, bufferToBigInt } from '../bigint-utils.js'
|
||||||
import { concatBuffers } from '../buffer-utils.js'
|
import { concatBuffers } from '../buffer-utils.js'
|
||||||
|
@ -49,7 +49,7 @@ export async function computeNewPasswordHash(
|
||||||
crypto.randomFill(salt1.subarray(algo.salt1.length))
|
crypto.randomFill(salt1.subarray(algo.salt1.length))
|
||||||
;(algo as tl.Mutable<typeof algo>).salt1 = salt1
|
;(algo as tl.Mutable<typeof algo>).salt1 = salt1
|
||||||
|
|
||||||
const _x = await computePasswordHash(crypto, utf8EncodeToBuffer(password), algo.salt1, algo.salt2)
|
const _x = await computePasswordHash(crypto, getPlatform().utf8Encode(password), algo.salt1, algo.salt2)
|
||||||
|
|
||||||
const g = BigInt(algo.g)
|
const g = BigInt(algo.g)
|
||||||
const p = bufferToBigInt(algo.p)
|
const p = bufferToBigInt(algo.p)
|
||||||
|
@ -103,7 +103,7 @@ export async function computeSrpParams(
|
||||||
|
|
||||||
const _k = crypto.sha256(concatBuffers([algo.p, _g]))
|
const _k = crypto.sha256(concatBuffers([algo.p, _g]))
|
||||||
const _u = crypto.sha256(concatBuffers([_gA, request.srpB]))
|
const _u = crypto.sha256(concatBuffers([_gA, request.srpB]))
|
||||||
const _x = await computePasswordHash(crypto, utf8EncodeToBuffer(password), algo.salt1, algo.salt2)
|
const _x = await computePasswordHash(crypto, getPlatform().utf8Encode(password), algo.salt1, algo.salt2)
|
||||||
const k = bufferToBigInt(_k)
|
const k = bufferToBigInt(_k)
|
||||||
const u = bufferToBigInt(_u)
|
const u = bufferToBigInt(_u)
|
||||||
const x = bufferToBigInt(_x)
|
const x = bufferToBigInt(_x)
|
||||||
|
|
|
@ -1,61 +1,62 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import { hexEncode, utf8Decode, utf8EncodeToBuffer } from '@mtcute/tl-runtime'
|
import { getPlatform } from '../../platform.js'
|
||||||
|
|
||||||
import { xorBuffer, xorBufferInPlace } from './utils.js'
|
import { xorBuffer, xorBufferInPlace } from './utils.js'
|
||||||
|
|
||||||
|
const p = getPlatform()
|
||||||
|
|
||||||
describe('xorBuffer', () => {
|
describe('xorBuffer', () => {
|
||||||
it('should xor buffers without modifying original', () => {
|
it('should xor buffers without modifying original', () => {
|
||||||
const data = utf8EncodeToBuffer('hello')
|
const data = p.utf8Encode('hello')
|
||||||
const key = utf8EncodeToBuffer('xor')
|
const key = p.utf8Encode('xor')
|
||||||
|
|
||||||
const xored = xorBuffer(data, key)
|
const xored = xorBuffer(data, key)
|
||||||
expect(utf8Decode(data)).eq('hello')
|
expect(p.utf8Decode(data)).eq('hello')
|
||||||
expect(utf8Decode(key)).eq('xor')
|
expect(p.utf8Decode(key)).eq('xor')
|
||||||
expect(hexEncode(xored)).eq('100a1e6c6f')
|
expect(p.hexEncode(xored)).eq('100a1e6c6f')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should be deterministic', () => {
|
it('should be deterministic', () => {
|
||||||
const data = utf8EncodeToBuffer('hello')
|
const data = p.utf8Encode('hello')
|
||||||
const key = utf8EncodeToBuffer('xor')
|
const key = p.utf8Encode('xor')
|
||||||
|
|
||||||
const xored1 = xorBuffer(data, key)
|
const xored1 = xorBuffer(data, key)
|
||||||
expect(hexEncode(xored1)).eq('100a1e6c6f')
|
expect(p.hexEncode(xored1)).eq('100a1e6c6f')
|
||||||
|
|
||||||
const xored2 = xorBuffer(data, key)
|
const xored2 = xorBuffer(data, key)
|
||||||
expect(hexEncode(xored2)).eq('100a1e6c6f')
|
expect(p.hexEncode(xored2)).eq('100a1e6c6f')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('second call should decode content', () => {
|
it('second call should decode content', () => {
|
||||||
const data = utf8EncodeToBuffer('hello')
|
const data = p.utf8Encode('hello')
|
||||||
const key = utf8EncodeToBuffer('xor')
|
const key = p.utf8Encode('xor')
|
||||||
|
|
||||||
const xored1 = xorBuffer(data, key)
|
const xored1 = xorBuffer(data, key)
|
||||||
expect(hexEncode(xored1)).eq('100a1e6c6f')
|
expect(p.hexEncode(xored1)).eq('100a1e6c6f')
|
||||||
|
|
||||||
const xored2 = xorBuffer(xored1, key)
|
const xored2 = xorBuffer(xored1, key)
|
||||||
expect(utf8Decode(xored2)).eq('hello')
|
expect(p.utf8Decode(xored2)).eq('hello')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('xorBufferInPlace', () => {
|
describe('xorBufferInPlace', () => {
|
||||||
it('should xor buffers by modifying original', () => {
|
it('should xor buffers by modifying original', () => {
|
||||||
const data = utf8EncodeToBuffer('hello')
|
const data = p.utf8Encode('hello')
|
||||||
const key = utf8EncodeToBuffer('xor')
|
const key = p.utf8Encode('xor')
|
||||||
|
|
||||||
xorBufferInPlace(data, key)
|
xorBufferInPlace(data, key)
|
||||||
expect(hexEncode(data)).eq('100a1e6c6f')
|
expect(p.hexEncode(data)).eq('100a1e6c6f')
|
||||||
expect(utf8Decode(key)).eq('xor')
|
expect(p.utf8Decode(key)).eq('xor')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('second call should decode content', () => {
|
it('second call should decode content', () => {
|
||||||
const data = utf8EncodeToBuffer('hello')
|
const data = p.utf8Encode('hello')
|
||||||
const key = utf8EncodeToBuffer('xor')
|
const key = p.utf8Encode('xor')
|
||||||
|
|
||||||
xorBufferInPlace(data, key)
|
xorBufferInPlace(data, key)
|
||||||
expect(hexEncode(data)).eq('100a1e6c6f')
|
expect(p.hexEncode(data)).eq('100a1e6c6f')
|
||||||
|
|
||||||
xorBufferInPlace(data, key)
|
xorBufferInPlace(data, key)
|
||||||
expect(utf8Decode(data)).eq('hello')
|
expect(p.utf8Decode(data)).eq('hello')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,36 +6,15 @@ import {
|
||||||
gunzip,
|
gunzip,
|
||||||
ige256Decrypt,
|
ige256Decrypt,
|
||||||
ige256Encrypt,
|
ige256Encrypt,
|
||||||
initAsync,
|
|
||||||
InitInput,
|
|
||||||
sha1,
|
sha1,
|
||||||
sha256,
|
sha256,
|
||||||
} from '@mtcute/wasm'
|
} from '@mtcute/wasm'
|
||||||
|
|
||||||
import { BaseCryptoProvider, IAesCtr, ICryptoProvider, IEncryptionScheme } from './abstract.js'
|
import { BaseCryptoProvider, IAesCtr, ICryptoProvider, IEncryptionScheme } from './abstract.js'
|
||||||
|
|
||||||
export interface WasmCryptoProviderOptions {
|
|
||||||
/**
|
|
||||||
* WASM blob to use for crypto operations.
|
|
||||||
*
|
|
||||||
* Must conform to `@mtcute/wasm` interface.
|
|
||||||
*/
|
|
||||||
wasmInput?: InitInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class WasmCryptoProvider extends BaseCryptoProvider implements Partial<ICryptoProvider> {
|
export abstract class WasmCryptoProvider extends BaseCryptoProvider implements Partial<ICryptoProvider> {
|
||||||
readonly wasmInput?: InitInput
|
|
||||||
|
|
||||||
abstract randomFill(buf: Uint8Array): void
|
abstract randomFill(buf: Uint8Array): void
|
||||||
|
abstract initialize(): Promise<void>
|
||||||
constructor(params?: WasmCryptoProviderOptions) {
|
|
||||||
super()
|
|
||||||
this.wasmInput = params?.wasmInput
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize(): Promise<void> {
|
|
||||||
return initAsync(this.wasmInput)
|
|
||||||
}
|
|
||||||
|
|
||||||
sha1(data: Uint8Array): Uint8Array {
|
sha1(data: Uint8Array): Uint8Array {
|
||||||
return sha1(data)
|
return sha1(data)
|
||||||
|
|
|
@ -49,7 +49,6 @@ export interface DcOptions {
|
||||||
media: BasicDcOption
|
media: BasicDcOption
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
export const defaultProductionDc: DcOptions = {
|
export const defaultProductionDc: DcOptions = {
|
||||||
main: {
|
main: {
|
||||||
ipAddress: '149.154.167.50',
|
ipAddress: '149.154.167.50',
|
||||||
|
@ -64,7 +63,6 @@ export const defaultProductionDc: DcOptions = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
export const defaultProductionIpv6Dc: DcOptions = {
|
export const defaultProductionIpv6Dc: DcOptions = {
|
||||||
main: {
|
main: {
|
||||||
ipAddress: '2001:067c:04e8:f002:0000:0000:0000:000a',
|
ipAddress: '2001:067c:04e8:f002:0000:0000:0000:000a',
|
||||||
|
@ -81,7 +79,6 @@ export const defaultProductionIpv6Dc: DcOptions = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
export const defaultTestDc: DcOptions = {
|
export const defaultTestDc: DcOptions = {
|
||||||
main: {
|
main: {
|
||||||
ipAddress: '149.154.167.40',
|
ipAddress: '149.154.167.40',
|
||||||
|
@ -96,7 +93,6 @@ export const defaultTestDc: DcOptions = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
export const defaultTestIpv6Dc: DcOptions = {
|
export const defaultTestIpv6Dc: DcOptions = {
|
||||||
main: {
|
main: {
|
||||||
ipAddress: '2001:67c:4e8:f002::e',
|
ipAddress: '2001:67c:4e8:f002::e',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
|
|
||||||
import { Logger } from '../logger.js'
|
import { Logger } from './logger.js'
|
||||||
|
|
||||||
export function reportUnknownError(log: Logger, error: tl.RpcError, method: string): void {
|
export function reportUnknownError(log: Logger, error: tl.RpcError, method: string): void {
|
||||||
if (typeof fetch !== 'function') return
|
if (typeof fetch !== 'function') return
|
|
@ -1,4 +1,10 @@
|
||||||
export * from '../highlevel/utils/index.js'
|
export * from '../highlevel/utils/index.js'
|
||||||
|
// todo: remove after 1.0.0
|
||||||
|
export * from '../highlevel/storage/service/current-user.js'
|
||||||
|
export * from '../highlevel/storage/service/updates.js'
|
||||||
|
export * from '../storage/service/base.js'
|
||||||
|
export * from '../storage/service/default-dcs.js'
|
||||||
|
// end todo
|
||||||
export * from './async-lock.js'
|
export * from './async-lock.js'
|
||||||
export * from './bigint-utils.js'
|
export * from './bigint-utils.js'
|
||||||
export * from './buffer-utils.js'
|
export * from './buffer-utils.js'
|
||||||
|
@ -17,7 +23,6 @@ export * from './lru-map.js'
|
||||||
export * from './lru-set.js'
|
export * from './lru-set.js'
|
||||||
export * from './misc-utils.js'
|
export * from './misc-utils.js'
|
||||||
export * from './peer-utils.js'
|
export * from './peer-utils.js'
|
||||||
export * from './platform/exit-hook.js'
|
|
||||||
export * from './sorted-array.js'
|
export * from './sorted-array.js'
|
||||||
export * from './tl-json.js'
|
export * from './tl-json.js'
|
||||||
export * from './type-assertions.js'
|
export * from './type-assertions.js'
|
||||||
|
|
|
@ -1,25 +1,6 @@
|
||||||
import { hexEncode } from '@mtcute/tl-runtime'
|
import { getPlatform } from '../platform.js'
|
||||||
|
|
||||||
import { _defaultLoggingHandler } from './platform/logging.js'
|
|
||||||
|
|
||||||
let defaultLogLevel = 3
|
|
||||||
|
|
||||||
/* c8 ignore start */
|
|
||||||
if (typeof process !== 'undefined') {
|
|
||||||
const envLogLevel = parseInt(process.env.MTCUTE_LOG_LEVEL ?? '')
|
|
||||||
|
|
||||||
if (!isNaN(envLogLevel)) {
|
|
||||||
defaultLogLevel = envLogLevel
|
|
||||||
}
|
|
||||||
} else if (typeof localStorage !== 'undefined') {
|
|
||||||
const localLogLevel = parseInt(localStorage.MTCUTE_LOG_LEVEL as string)
|
|
||||||
|
|
||||||
if (!isNaN(localLogLevel)) {
|
|
||||||
defaultLogLevel = localLogLevel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* c8 ignore end */
|
|
||||||
|
|
||||||
|
const DEFAULT_LOG_LEVEL = 3
|
||||||
const FORMATTER_RE = /%[a-zA-Z]/g
|
const FORMATTER_RE = /%[a-zA-Z]/g
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -81,7 +62,7 @@ export class Logger {
|
||||||
args.splice(idx, 1)
|
args.splice(idx, 1)
|
||||||
|
|
||||||
if (m === '%h') {
|
if (m === '%h') {
|
||||||
if (ArrayBuffer.isView(val)) return hexEncode(val as Uint8Array)
|
if (ArrayBuffer.isView(val)) return this.mgr.platform.hexEncode(val as Uint8Array)
|
||||||
if (typeof val === 'number' || typeof val === 'bigint') return val.toString(16)
|
if (typeof val === 'number' || typeof val === 'bigint') return val.toString(16)
|
||||||
|
|
||||||
return String(val)
|
return String(val)
|
||||||
|
@ -96,10 +77,10 @@ export class Logger {
|
||||||
return JSON.stringify(val, (k, v) => {
|
return JSON.stringify(val, (k, v) => {
|
||||||
if (
|
if (
|
||||||
ArrayBuffer.isView(v) ||
|
ArrayBuffer.isView(v) ||
|
||||||
(typeof v === 'object' && v.type === 'Buffer' && Array.isArray(v.data))
|
(typeof v === 'object' && v.type === 'Buffer' && Array.isArray(v.data)) // todo: how can we do this better?
|
||||||
) {
|
) {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
let str = v.data ? Buffer.from(v.data as number[]).toString('hex') : hexEncode(v)
|
let str = v.data ? Buffer.from(v.data as number[]).toString('hex') : this.mgr.platform.hexEncode(v)
|
||||||
|
|
||||||
if (str.length > 300) {
|
if (str.length > 300) {
|
||||||
str = str.slice(0, 300) + '...'
|
str = str.slice(0, 300) + '...'
|
||||||
|
@ -171,8 +152,10 @@ export class LogManager extends Logger {
|
||||||
|
|
||||||
private _filter: (tag: string) => boolean = defaultFilter
|
private _filter: (tag: string) => boolean = defaultFilter
|
||||||
|
|
||||||
level = defaultLogLevel
|
readonly platform = getPlatform()
|
||||||
handler = _defaultLoggingHandler
|
|
||||||
|
level = this.platform.getDefaultLogLevel() ?? DEFAULT_LOG_LEVEL
|
||||||
|
handler = this.platform.log.bind(this.platform)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a {@link Logger} with the given tag
|
* Create a {@link Logger} with the given tag
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
import { NodeCryptoProvider } from '../crypto/node.js'
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
export const _defaultCryptoProviderFactory = () => new NodeCryptoProvider()
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { MtUnsupportedError } from '../../index.js'
|
|
||||||
import { WebCryptoProvider } from '../crypto/web.js'
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
export const _defaultCryptoProviderFactory = () => {
|
|
||||||
if (typeof crypto === 'undefined' || typeof crypto.subtle === 'undefined') {
|
|
||||||
throw new MtUnsupportedError('WebCrypto API is not available')
|
|
||||||
}
|
|
||||||
|
|
||||||
return new WebCryptoProvider({ crypto })
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
import { TcpTransport } from '../../network/transports/tcp.js'
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
export const _defaultTransportFactory = () => new TcpTransport()
|
|
|
@ -1,14 +0,0 @@
|
||||||
import { WebSocketTransport } from '../../network/transports/websocket.js'
|
|
||||||
import { MtUnsupportedError } from '../../types/index.js'
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
export const _defaultTransportFactory =
|
|
||||||
// if no websocket, throw an error i guess ¯\_(ツ)_/¯
|
|
||||||
// (user can still implement something on their own)
|
|
||||||
typeof WebSocket === 'undefined' ?
|
|
||||||
() => {
|
|
||||||
throw new MtUnsupportedError(
|
|
||||||
'Neither TCP nor WebSocket are available. Please pass a Transport factory explicitly',
|
|
||||||
)
|
|
||||||
} :
|
|
||||||
() => new WebSocketTransport()
|
|
|
@ -19,20 +19,24 @@
|
||||||
"keepScripts": [
|
"keepScripts": [
|
||||||
"install"
|
"install"
|
||||||
],
|
],
|
||||||
|
"exports": {
|
||||||
|
".": "./src/index.ts",
|
||||||
|
"./native.js": "./src/native.ts"
|
||||||
|
},
|
||||||
"distOnlyFields": {
|
"distOnlyFields": {
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"import": "./esm/index.js",
|
"import": "./esm/index.js",
|
||||||
"require": "./cjs/index.js"
|
"require": "./cjs/index.js"
|
||||||
},
|
},
|
||||||
"./native": {
|
"./native.js": {
|
||||||
"import": "./esm/native.cjs",
|
"import": "./esm/native.cjs",
|
||||||
"require": "./cjs/native.cjs"
|
"require": "./cjs/native.cjs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mtcute/core": "workspace:^"
|
"@mtcute/node": "workspace:^"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@mtcute/test": "workspace:^"
|
"@mtcute/test": "workspace:^"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { BaseNodeCryptoProvider } from '@mtcute/core/src/utils/crypto/node.js'
|
import { BaseNodeCryptoProvider } from '@mtcute/node'
|
||||||
import { IEncryptionScheme } from '@mtcute/core/utils.js'
|
import { IEncryptionScheme } from '@mtcute/node/utils.js'
|
||||||
|
|
||||||
import { native } from './native.cjs'
|
import { native } from './native.cjs'
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import { CallbackQuery, MtArgumentError, PeersIndex } from '@mtcute/core'
|
import { CallbackQuery, MtArgumentError, PeersIndex } from '@mtcute/core'
|
||||||
import { utf8EncodeToBuffer } from '@mtcute/core/utils.js'
|
import { getPlatform } from '@mtcute/core/platform.js'
|
||||||
import { createStub } from '@mtcute/test'
|
import { createStub } from '@mtcute/test'
|
||||||
|
|
||||||
import { CallbackDataBuilder } from './callback-data-builder.js'
|
import { CallbackDataBuilder } from './callback-data-builder.js'
|
||||||
|
@ -53,7 +53,7 @@ describe('CallbackDataBuilder', () => {
|
||||||
const createCb = (data: string) =>
|
const createCb = (data: string) =>
|
||||||
new CallbackQuery(
|
new CallbackQuery(
|
||||||
createStub('updateBotCallbackQuery', {
|
createStub('updateBotCallbackQuery', {
|
||||||
data: utf8EncodeToBuffer(data),
|
data: getPlatform().utf8Encode(data),
|
||||||
}),
|
}),
|
||||||
new PeersIndex(),
|
new PeersIndex(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { ParsedUpdate, TelegramClient } from '@mtcute/core'
|
import { ParsedUpdate } from '@mtcute/core'
|
||||||
|
import { TelegramClient } from '@mtcute/core/client.js'
|
||||||
|
|
||||||
export type UpdateContext<T> = T & {
|
export type UpdateContext<T> = T & {
|
||||||
client: TelegramClient
|
client: TelegramClient
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { CallbackQuery, InlineCallbackQuery, MaybePromise, Message, TelegramClient } from '@mtcute/core'
|
import { CallbackQuery, InlineCallbackQuery, MaybePromise, Message } from '@mtcute/core'
|
||||||
|
import { TelegramClient } from '@mtcute/core/client.js'
|
||||||
|
|
||||||
import { UpdateContext } from './base.js'
|
import { UpdateContext } from './base.js'
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { BotChatJoinRequestUpdate, TelegramClient } from '@mtcute/core'
|
import { BotChatJoinRequestUpdate } from '@mtcute/core'
|
||||||
|
import { TelegramClient } from '@mtcute/core/client.js'
|
||||||
|
|
||||||
import { UpdateContext } from './base.js'
|
import { UpdateContext } from './base.js'
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { ChosenInlineResult, MtArgumentError, TelegramClient } from '@mtcute/core'
|
import { ChosenInlineResult, MtArgumentError } from '@mtcute/core'
|
||||||
|
import { TelegramClient } from '@mtcute/core/client.js'
|
||||||
|
|
||||||
import { UpdateContext } from './base.js'
|
import { UpdateContext } from './base.js'
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { InlineQuery, ParametersSkip1, TelegramClient } from '@mtcute/core'
|
import { InlineQuery, ParametersSkip1 } from '@mtcute/core'
|
||||||
|
import { TelegramClient } from '@mtcute/core/client.js'
|
||||||
|
|
||||||
import { UpdateContext } from './base.js'
|
import { UpdateContext } from './base.js'
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
import { Message, MtPeerNotFoundError, OmitInputMessageId, ParametersSkip1, Peer, TelegramClient } from '@mtcute/core'
|
import { Message, MtPeerNotFoundError, OmitInputMessageId, ParametersSkip1, Peer } from '@mtcute/core'
|
||||||
// todo: fix these imports when packaging
|
import { TelegramClient } from '@mtcute/core/client.js'
|
||||||
import { DeleteMessagesParams } from '@mtcute/core/src/highlevel/methods/messages/delete-messages.js'
|
import { DeleteMessagesParams, ForwardMessageOptions, SendCopyGroupParams, SendCopyParams } from '@mtcute/core/methods.js'
|
||||||
import { ForwardMessageOptions } from '@mtcute/core/src/highlevel/methods/messages/forward-messages.js'
|
|
||||||
import { SendCopyParams } from '@mtcute/core/src/highlevel/methods/messages/send-copy.js'
|
|
||||||
import { SendCopyGroupParams } from '@mtcute/core/src/highlevel/methods/messages/send-copy-group.js'
|
|
||||||
|
|
||||||
import { UpdateContext } from './base.js'
|
import { UpdateContext } from './base.js'
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { ParsedUpdate, TelegramClient } from '@mtcute/core'
|
import { ParsedUpdate } from '@mtcute/core'
|
||||||
|
import { TelegramClient } from '@mtcute/core/client.js'
|
||||||
|
|
||||||
import { UpdateContextDistributed } from './base.js'
|
import { UpdateContextDistributed } from './base.js'
|
||||||
import { CallbackQueryContext } from './callback-query.js'
|
import { CallbackQueryContext } from './callback-query.js'
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { PreCheckoutQuery, TelegramClient } from '@mtcute/core'
|
import { PreCheckoutQuery } from '@mtcute/core'
|
||||||
|
import { TelegramClient } from '@mtcute/core/client.js'
|
||||||
|
|
||||||
import { UpdateContext } from './base.js'
|
import { UpdateContext } from './base.js'
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,11 @@ import {
|
||||||
PollUpdate,
|
PollUpdate,
|
||||||
PollVoteUpdate,
|
PollVoteUpdate,
|
||||||
StoryUpdate,
|
StoryUpdate,
|
||||||
TelegramClient,
|
|
||||||
tl,
|
tl,
|
||||||
UserStatusUpdate,
|
UserStatusUpdate,
|
||||||
UserTypingUpdate,
|
UserTypingUpdate,
|
||||||
} from '@mtcute/core'
|
} from '@mtcute/core'
|
||||||
|
import { TelegramClient } from '@mtcute/core/client.js'
|
||||||
|
|
||||||
import { UpdateContext } from './context/base.js'
|
import { UpdateContext } from './context/base.js'
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -12,11 +12,11 @@ import {
|
||||||
PollUpdate,
|
PollUpdate,
|
||||||
PollVoteUpdate,
|
PollVoteUpdate,
|
||||||
StoryUpdate,
|
StoryUpdate,
|
||||||
TelegramClient,
|
|
||||||
tl,
|
tl,
|
||||||
UserStatusUpdate,
|
UserStatusUpdate,
|
||||||
UserTypingUpdate,
|
UserTypingUpdate,
|
||||||
} from '@mtcute/core'
|
} from '@mtcute/core'
|
||||||
|
import { TelegramClient } from '@mtcute/core/client.js'
|
||||||
|
|
||||||
import { UpdateContext } from './context/base.js'
|
import { UpdateContext } from './context/base.js'
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -21,5 +21,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mtcute/tl-runtime": "workspace:^",
|
"@mtcute/tl-runtime": "workspace:^",
|
||||||
"long": "5.2.3"
|
"long": "5.2.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@mtcute/test": "workspace:^"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Long from 'long'
|
import Long from 'long'
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import { hexDecodeToBuffer } from '@mtcute/tl-runtime'
|
import { defaultPlatform } from '@mtcute/test'
|
||||||
|
|
||||||
import { parseFileId } from './parse.js'
|
import { parseFileId } from './parse.js'
|
||||||
import { tdFileId as td } from './types.js'
|
import { tdFileId as td } from './types.js'
|
||||||
|
@ -10,14 +10,14 @@ import { tdFileId as td } from './types.js'
|
||||||
|
|
||||||
describe('parsing file ids', () => {
|
describe('parsing file ids', () => {
|
||||||
const test = (id: string, expected: td.RawFullRemoteFileLocation) => {
|
const test = (id: string, expected: td.RawFullRemoteFileLocation) => {
|
||||||
expect(parseFileId(id)).eql(expected)
|
expect(parseFileId(defaultPlatform, id)).eql(expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
it('parses common file ids', () => {
|
it('parses common file ids', () => {
|
||||||
test('CAACAgIAAxkBAAEJny9gituz1_V_uSKBUuG_nhtzEtFOeQACXFoAAuCjggfYjw_KAAGSnkgfBA', {
|
test('CAACAgIAAxkBAAEJny9gituz1_V_uSKBUuG_nhtzEtFOeQACXFoAAuCjggfYjw_KAAGSnkgfBA', {
|
||||||
_: 'remoteFileLocation',
|
_: 'remoteFileLocation',
|
||||||
dcId: 2,
|
dcId: 2,
|
||||||
fileReference: hexDecodeToBuffer('0100099f2f608adbb3d7f57fb9228152e1bf9e1b7312d14e79'),
|
fileReference: defaultPlatform.hexDecode('0100099f2f608adbb3d7f57fb9228152e1bf9e1b7312d14e79'),
|
||||||
location: {
|
location: {
|
||||||
_: 'common',
|
_: 'common',
|
||||||
accessHash: Long.fromString('5232780349138767832'),
|
accessHash: Long.fromString('5232780349138767832'),
|
||||||
|
@ -28,7 +28,7 @@ describe('parsing file ids', () => {
|
||||||
test('BQACAgIAAxkBAAEJnzNgit00IDsKd07OdSeanwz8osecYAACdAwAAueoWEicaPvNdOYEwB8E', {
|
test('BQACAgIAAxkBAAEJnzNgit00IDsKd07OdSeanwz8osecYAACdAwAAueoWEicaPvNdOYEwB8E', {
|
||||||
_: 'remoteFileLocation',
|
_: 'remoteFileLocation',
|
||||||
dcId: 2,
|
dcId: 2,
|
||||||
fileReference: hexDecodeToBuffer('0100099f33608add34203b0a774ece75279a9f0cfca2c79c60'),
|
fileReference: defaultPlatform.hexDecode('0100099f33608add34203b0a774ece75279a9f0cfca2c79c60'),
|
||||||
location: {
|
location: {
|
||||||
_: 'common',
|
_: 'common',
|
||||||
accessHash: Long.fromString('-4610306729174144868'),
|
accessHash: Long.fromString('-4610306729174144868'),
|
||||||
|
@ -42,7 +42,7 @@ describe('parsing file ids', () => {
|
||||||
test('AAMCAgADGQEAAQmfL2CK27PX9X-5IoFS4b-eG3MS0U55AAJcWgAC4KOCB9iPD8oAAZKeSK1c8w4ABAEAB20AA1kCAAIfBA', {
|
test('AAMCAgADGQEAAQmfL2CK27PX9X-5IoFS4b-eG3MS0U55AAJcWgAC4KOCB9iPD8oAAZKeSK1c8w4ABAEAB20AA1kCAAIfBA', {
|
||||||
_: 'remoteFileLocation',
|
_: 'remoteFileLocation',
|
||||||
dcId: 2,
|
dcId: 2,
|
||||||
fileReference: hexDecodeToBuffer('0100099f2f608adbb3d7f57fb9228152e1bf9e1b7312d14e79'),
|
fileReference: defaultPlatform.hexDecode('0100099f2f608adbb3d7f57fb9228152e1bf9e1b7312d14e79'),
|
||||||
location: {
|
location: {
|
||||||
_: 'photo',
|
_: 'photo',
|
||||||
accessHash: Long.fromString('5232780349138767832'),
|
accessHash: Long.fromString('5232780349138767832'),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { base64DecodeToBuffer, base64Encode, TlBinaryReader } from '@mtcute/tl-runtime'
|
import { ITlPlatform, TlBinaryReader } from '@mtcute/tl-runtime'
|
||||||
|
|
||||||
import { tdFileId as td } from './types.js'
|
import { tdFileId as td } from './types.js'
|
||||||
import { telegramRleDecode } from './utils.js'
|
import { telegramRleDecode } from './utils.js'
|
||||||
|
@ -11,7 +11,7 @@ function parseWebFileLocation(reader: TlBinaryReader): td.RawWebRemoteFileLocati
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parsePhotoSizeSource(reader: TlBinaryReader): td.TypePhotoSizeSource {
|
function parsePhotoSizeSource(platform: ITlPlatform, reader: TlBinaryReader): td.TypePhotoSizeSource {
|
||||||
const variant = reader.int()
|
const variant = reader.int()
|
||||||
|
|
||||||
switch (variant) {
|
switch (variant) {
|
||||||
|
@ -24,14 +24,16 @@ function parsePhotoSizeSource(reader: TlBinaryReader): td.TypePhotoSizeSource {
|
||||||
const fileType = reader.int()
|
const fileType = reader.int()
|
||||||
|
|
||||||
if (fileType < 0 || fileType >= td.FileType.Size) {
|
if (fileType < 0 || fileType >= td.FileType.Size) {
|
||||||
throw new td.UnsupportedError(`Unsupported file type: ${fileType} (${base64Encode(reader.uint8View)})`)
|
throw new td.UnsupportedError(
|
||||||
|
`Unsupported file type: ${fileType} (${platform.base64Encode(reader.uint8View)})`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const thumbnailType = reader.int()
|
const thumbnailType = reader.int()
|
||||||
|
|
||||||
if (thumbnailType < 0 || thumbnailType > 255) {
|
if (thumbnailType < 0 || thumbnailType > 255) {
|
||||||
throw new td.InvalidFileIdError(
|
throw new td.InvalidFileIdError(
|
||||||
`Wrong thumbnail type: ${thumbnailType} (${base64Encode(reader.uint8View)})`,
|
`Wrong thumbnail type: ${thumbnailType} (${platform.base64Encode(reader.uint8View)})`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,24 +112,28 @@ function parsePhotoSizeSource(reader: TlBinaryReader): td.TypePhotoSizeSource {
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new td.UnsupportedError(
|
throw new td.UnsupportedError(
|
||||||
`Unsupported photo size source ${variant} (${base64Encode(reader.uint8View)})`,
|
`Unsupported photo size source ${variant} (${platform.base64Encode(reader.uint8View)})`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parsePhotoFileLocation(reader: TlBinaryReader, version: number): td.RawPhotoRemoteFileLocation {
|
function parsePhotoFileLocation(
|
||||||
|
platform: ITlPlatform,
|
||||||
|
reader: TlBinaryReader,
|
||||||
|
version: number,
|
||||||
|
): td.RawPhotoRemoteFileLocation {
|
||||||
const id = reader.long()
|
const id = reader.long()
|
||||||
const accessHash = reader.long()
|
const accessHash = reader.long()
|
||||||
let source: td.TypePhotoSizeSource
|
let source: td.TypePhotoSizeSource
|
||||||
|
|
||||||
if (version >= 32) {
|
if (version >= 32) {
|
||||||
source = parsePhotoSizeSource(reader)
|
source = parsePhotoSizeSource(platform, reader)
|
||||||
} else {
|
} else {
|
||||||
const volumeId = reader.long()
|
const volumeId = reader.long()
|
||||||
let localId = 0
|
let localId = 0
|
||||||
|
|
||||||
if (version >= 22) {
|
if (version >= 22) {
|
||||||
source = parsePhotoSizeSource(reader)
|
source = parsePhotoSizeSource(platform, reader)
|
||||||
localId = reader.int()
|
localId = reader.int()
|
||||||
} else {
|
} else {
|
||||||
source = {
|
source = {
|
||||||
|
@ -190,9 +196,9 @@ function parseCommonFileLocation(reader: TlBinaryReader): td.RawCommonRemoteFile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fromPersistentIdV23(binary: Uint8Array, version: number): td.RawFullRemoteFileLocation {
|
function fromPersistentIdV23(platform: ITlPlatform, binary: Uint8Array, version: number): td.RawFullRemoteFileLocation {
|
||||||
if (version < 0 || version > td.CURRENT_VERSION) {
|
if (version < 0 || version > td.CURRENT_VERSION) {
|
||||||
throw new td.UnsupportedError(`Unsupported file ID v3 subversion: ${version} (${base64Encode(binary)})`)
|
throw new td.UnsupportedError(`Unsupported file ID v3 subversion: ${version} (${platform.base64Encode(binary)})`)
|
||||||
}
|
}
|
||||||
|
|
||||||
binary = telegramRleDecode(binary)
|
binary = telegramRleDecode(binary)
|
||||||
|
@ -208,7 +214,7 @@ function fromPersistentIdV23(binary: Uint8Array, version: number): td.RawFullRem
|
||||||
fileType &= ~td.FILE_REFERENCE_FLAG
|
fileType &= ~td.FILE_REFERENCE_FLAG
|
||||||
|
|
||||||
if (fileType < 0 || fileType >= td.FileType.Size) {
|
if (fileType < 0 || fileType >= td.FileType.Size) {
|
||||||
throw new td.UnsupportedError(`Unsupported file type: ${fileType} (${base64Encode(binary)})`)
|
throw new td.UnsupportedError(`Unsupported file type: ${fileType} (${platform.base64Encode(binary)})`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const dcId = reader.int()
|
const dcId = reader.int()
|
||||||
|
@ -237,7 +243,7 @@ function fromPersistentIdV23(binary: Uint8Array, version: number): td.RawFullRem
|
||||||
case td.FileType.EncryptedThumbnail:
|
case td.FileType.EncryptedThumbnail:
|
||||||
case td.FileType.Wallpaper: {
|
case td.FileType.Wallpaper: {
|
||||||
// location_type = photo
|
// location_type = photo
|
||||||
location = parsePhotoFileLocation(reader, version)
|
location = parsePhotoFileLocation(platform, reader, version)
|
||||||
|
|
||||||
// validate
|
// validate
|
||||||
switch (location.source._) {
|
switch (location.source._) {
|
||||||
|
@ -287,7 +293,7 @@ function fromPersistentIdV23(binary: Uint8Array, version: number): td.RawFullRem
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new td.UnsupportedError(`Invalid file type: ${fileType} (${base64Encode(binary)})`)
|
throw new td.UnsupportedError(`Invalid file type: ${fileType} (${platform.base64Encode(binary)})`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,14 +306,14 @@ function fromPersistentIdV23(binary: Uint8Array, version: number): td.RawFullRem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fromPersistentIdV2(binary: Uint8Array) {
|
function fromPersistentIdV2(platform: ITlPlatform, binary: Uint8Array) {
|
||||||
return fromPersistentIdV23(binary.subarray(0, -1), 0)
|
return fromPersistentIdV23(platform, binary.subarray(0, -1), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
function fromPersistentIdV3(binary: Uint8Array) {
|
function fromPersistentIdV3(platform: ITlPlatform, binary: Uint8Array) {
|
||||||
const subversion = binary[binary.length - 2]
|
const subversion = binary[binary.length - 2]
|
||||||
|
|
||||||
return fromPersistentIdV23(binary.subarray(0, -2), subversion)
|
return fromPersistentIdV23(platform, binary.subarray(0, -2), subversion)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -315,18 +321,18 @@ function fromPersistentIdV3(binary: Uint8Array) {
|
||||||
*
|
*
|
||||||
* @param fileId File ID as a base-64 encoded string or Buffer
|
* @param fileId File ID as a base-64 encoded string or Buffer
|
||||||
*/
|
*/
|
||||||
export function parseFileId(fileId: string | Uint8Array): td.RawFullRemoteFileLocation {
|
export function parseFileId(platform: ITlPlatform, fileId: string | Uint8Array): td.RawFullRemoteFileLocation {
|
||||||
if (typeof fileId === 'string') fileId = base64DecodeToBuffer(fileId, true)
|
if (typeof fileId === 'string') fileId = platform.base64Decode(fileId, true)
|
||||||
|
|
||||||
const version = fileId[fileId.length - 1]
|
const version = fileId[fileId.length - 1]
|
||||||
|
|
||||||
if (version === td.PERSISTENT_ID_VERSION_OLD) {
|
if (version === td.PERSISTENT_ID_VERSION_OLD) {
|
||||||
return fromPersistentIdV2(fileId)
|
return fromPersistentIdV2(platform, fileId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version === td.PERSISTENT_ID_VERSION) {
|
if (version === td.PERSISTENT_ID_VERSION) {
|
||||||
return fromPersistentIdV3(fileId)
|
return fromPersistentIdV3(platform, fileId)
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new td.UnsupportedError(`Unsupported file ID version: ${version} (${base64Encode(fileId)})`)
|
throw new td.UnsupportedError(`Unsupported file ID version: ${version} (${platform.base64Encode(fileId)})`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import { defaultPlatform } from '@mtcute/test'
|
||||||
|
|
||||||
import { parseFileId } from './parse.js'
|
import { parseFileId } from './parse.js'
|
||||||
import { toUniqueFileId } from './serialize-unique.js'
|
import { toUniqueFileId } from './serialize-unique.js'
|
||||||
|
|
||||||
|
@ -7,7 +9,7 @@ import { toUniqueFileId } from './serialize-unique.js'
|
||||||
|
|
||||||
describe('serializing unique file ids', () => {
|
describe('serializing unique file ids', () => {
|
||||||
const test = (id: string, expected: string) => {
|
const test = (id: string, expected: string) => {
|
||||||
expect(toUniqueFileId(parseFileId(id))).eql(expected)
|
expect(toUniqueFileId(defaultPlatform, parseFileId(defaultPlatform, id))).eql(expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
it('serializes unique ids for old file ids', () => {
|
it('serializes unique ids for old file ids', () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { base64Encode, byteLengthUtf8, TlBinaryWriter } from '@mtcute/tl-runtime'
|
import { ITlPlatform, TlBinaryWriter } from '@mtcute/tl-runtime'
|
||||||
|
|
||||||
import { tdFileId as td } from './types.js'
|
import { tdFileId as td } from './types.js'
|
||||||
import { assertNever, telegramRleEncode } from './utils.js'
|
import { assertNever, telegramRleEncode } from './utils.js'
|
||||||
|
@ -20,10 +20,11 @@ export type InputUniqueLocation =
|
||||||
*
|
*
|
||||||
* @param location Information about file location
|
* @param location Information about file location
|
||||||
*/
|
*/
|
||||||
export function toUniqueFileId(location: Omit<td.RawFullRemoteFileLocation, '_'>): string
|
export function toUniqueFileId(platform: ITlPlatform, location: Omit<td.RawFullRemoteFileLocation, '_'>): string
|
||||||
export function toUniqueFileId(type: td.FileType, location: InputUniqueLocation): string
|
export function toUniqueFileId(platform: ITlPlatform, type: td.FileType, location: InputUniqueLocation): string
|
||||||
|
|
||||||
export function toUniqueFileId(
|
export function toUniqueFileId(
|
||||||
|
platform: ITlPlatform,
|
||||||
first: td.FileType | Omit<td.RawFullRemoteFileLocation, '_'>,
|
first: td.FileType | Omit<td.RawFullRemoteFileLocation, '_'>,
|
||||||
second?: InputUniqueLocation,
|
second?: InputUniqueLocation,
|
||||||
): string {
|
): string {
|
||||||
|
@ -140,7 +141,7 @@ export function toUniqueFileId(
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'web':
|
case 'web':
|
||||||
writer = TlBinaryWriter.alloc(undefined, byteLengthUtf8(inputLocation.url) + 8)
|
writer = TlBinaryWriter.manual(platform.utf8ByteLength(inputLocation.url) + 8)
|
||||||
writer.int(type)
|
writer.int(type)
|
||||||
writer.string(inputLocation.url)
|
writer.string(inputLocation.url)
|
||||||
break
|
break
|
||||||
|
@ -153,5 +154,5 @@ export function toUniqueFileId(
|
||||||
assertNever(inputLocation)
|
assertNever(inputLocation)
|
||||||
}
|
}
|
||||||
|
|
||||||
return base64Encode(telegramRleEncode(writer.result()), true)
|
return platform.base64Encode(telegramRleEncode(writer.result()), true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { base64Encode, byteLengthUtf8, TlBinaryWriter } from '@mtcute/tl-runtime'
|
import { ITlPlatform, TlBinaryWriter } from '@mtcute/tl-runtime'
|
||||||
|
|
||||||
import { tdFileId as td } from './types.js'
|
import { tdFileId as td } from './types.js'
|
||||||
import { assertNever, telegramRleEncode } from './utils.js'
|
import { assertNever, telegramRleEncode } from './utils.js'
|
||||||
|
@ -11,7 +11,7 @@ const SUFFIX = new Uint8Array([td.CURRENT_VERSION, td.PERSISTENT_ID_VERSION])
|
||||||
*
|
*
|
||||||
* @param location Information about file location
|
* @param location Information about file location
|
||||||
*/
|
*/
|
||||||
export function toFileId(location: Omit<td.RawFullRemoteFileLocation, '_'>): string {
|
export function toFileId(platform: ITlPlatform, location: Omit<td.RawFullRemoteFileLocation, '_'>): string {
|
||||||
const loc = location.location
|
const loc = location.location
|
||||||
|
|
||||||
let type: number = location.type
|
let type: number = location.type
|
||||||
|
@ -25,7 +25,7 @@ export function toFileId(location: Omit<td.RawFullRemoteFileLocation, '_'>): str
|
||||||
//
|
//
|
||||||
// longest file ids are around 80 bytes, so i guess
|
// longest file ids are around 80 bytes, so i guess
|
||||||
// we are safe with allocating 100 bytes
|
// we are safe with allocating 100 bytes
|
||||||
const writer = TlBinaryWriter.alloc(undefined, loc._ === 'web' ? byteLengthUtf8(loc.url) + 32 : 100)
|
const writer = TlBinaryWriter.manual(loc._ === 'web' ? platform.utf8ByteLength(loc.url) + 32 : 100)
|
||||||
|
|
||||||
writer.int(type)
|
writer.int(type)
|
||||||
writer.int(location.dcId)
|
writer.int(location.dcId)
|
||||||
|
@ -108,5 +108,5 @@ export function toFileId(location: Omit<td.RawFullRemoteFileLocation, '_'>): str
|
||||||
withSuffix.set(result)
|
withSuffix.set(result)
|
||||||
withSuffix.set(SUFFIX, result.length)
|
withSuffix.set(SUFFIX, result.length)
|
||||||
|
|
||||||
return base64Encode(withSuffix, true)
|
return platform.base64Encode(withSuffix, true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,34 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import { hexDecodeToBuffer, hexEncode } from '@mtcute/tl-runtime'
|
import { defaultPlatform } from '@mtcute/test'
|
||||||
|
|
||||||
import { telegramRleDecode, telegramRleEncode } from './utils.js'
|
import { telegramRleDecode, telegramRleEncode } from './utils.js'
|
||||||
|
|
||||||
|
const p = defaultPlatform
|
||||||
|
|
||||||
describe('telegramRleEncode', () => {
|
describe('telegramRleEncode', () => {
|
||||||
it('should not modify input if there are no \\x00', () => {
|
it('should not modify input if there are no \\x00', () => {
|
||||||
expect(hexEncode(telegramRleEncode(hexDecodeToBuffer('aaeeff')))).eq('aaeeff')
|
expect(p.hexEncode(telegramRleEncode(p.hexDecode('aaeeff')))).eq('aaeeff')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should collapse consecutive \\x00', () => {
|
it('should collapse consecutive \\x00', () => {
|
||||||
expect(hexEncode(telegramRleEncode(hexDecodeToBuffer('00000000aa')))).eq('0004aa')
|
expect(p.hexEncode(telegramRleEncode(p.hexDecode('00000000aa')))).eq('0004aa')
|
||||||
expect(hexEncode(telegramRleEncode(hexDecodeToBuffer('00000000aa000000aa')))).eq('0004aa0003aa')
|
expect(p.hexEncode(telegramRleEncode(p.hexDecode('00000000aa000000aa')))).eq('0004aa0003aa')
|
||||||
expect(hexEncode(telegramRleEncode(hexDecodeToBuffer('00000000aa0000')))).eq('0004aa0002')
|
expect(p.hexEncode(telegramRleEncode(p.hexDecode('00000000aa0000')))).eq('0004aa0002')
|
||||||
expect(hexEncode(telegramRleEncode(hexDecodeToBuffer('00aa00')))).eq('0001aa0001')
|
expect(p.hexEncode(telegramRleEncode(p.hexDecode('00aa00')))).eq('0001aa0001')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('telegramRleDecode', () => {
|
describe('telegramRleDecode', () => {
|
||||||
it('should not mofify input if there are no \\x00', () => {
|
it('should not mofify input if there are no \\x00', () => {
|
||||||
expect(hexEncode(telegramRleDecode(hexDecodeToBuffer('aaeeff')))).eq('aaeeff')
|
expect(p.hexEncode(telegramRleDecode(p.hexDecode('aaeeff')))).eq('aaeeff')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should expand two-byte sequences starting with \\x00', () => {
|
it('should expand two-byte sequences starting with \\x00', () => {
|
||||||
expect(hexEncode(telegramRleDecode(hexDecodeToBuffer('0004aa')))).eq('00000000aa')
|
expect(p.hexEncode(telegramRleDecode(p.hexDecode('0004aa')))).eq('00000000aa')
|
||||||
expect(hexEncode(telegramRleDecode(hexDecodeToBuffer('0004aa0000')))).eq('00000000aa')
|
expect(p.hexEncode(telegramRleDecode(p.hexDecode('0004aa0000')))).eq('00000000aa')
|
||||||
expect(hexEncode(telegramRleDecode(hexDecodeToBuffer('0004aa0003aa')))).eq('00000000aa000000aa')
|
expect(p.hexEncode(telegramRleDecode(p.hexDecode('0004aa0003aa')))).eq('00000000aa000000aa')
|
||||||
expect(hexEncode(telegramRleDecode(hexDecodeToBuffer('0004aa0002')))).eq('00000000aa0000')
|
expect(p.hexEncode(telegramRleDecode(p.hexDecode('0004aa0002')))).eq('00000000aa0000')
|
||||||
expect(hexEncode(telegramRleDecode(hexDecodeToBuffer('0001aa0001')))).eq('00aa00')
|
expect(p.hexEncode(telegramRleDecode(p.hexDecode('0001aa0001')))).eq('00aa00')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import { connect as connectTcp } from 'net'
|
import { connect as connectTcp } from 'net'
|
||||||
import { connect as connectTls, SecureContextOptions } from 'tls'
|
import { connect as connectTls, SecureContextOptions } from 'tls'
|
||||||
|
|
||||||
import { IntermediatePacketCodec, MtcuteError, tl, TransportState } from '@mtcute/core'
|
import { BaseTcpTransport, IntermediatePacketCodec, MtcuteError, NodePlatform, tl, TransportState } from '@mtcute/node'
|
||||||
import { BaseTcpTransport } from '@mtcute/core/src/network/transports/tcp.js'
|
|
||||||
import { base64Encode, utf8EncodeToBuffer } from '@mtcute/core/utils.js'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An error has occurred while connecting to an HTTP(s) proxy
|
* An error has occurred while connecting to an HTTP(s) proxy
|
||||||
|
@ -71,6 +69,8 @@ export abstract class BaseHttpProxyTcpTransport extends BaseTcpTransport {
|
||||||
this._proxy = proxy
|
this._proxy = proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _platform = new NodePlatform()
|
||||||
|
|
||||||
connect(dc: tl.RawDcOption): void {
|
connect(dc: tl.RawDcOption): void {
|
||||||
if (this._state !== TransportState.Idle) {
|
if (this._state !== TransportState.Idle) {
|
||||||
throw new MtcuteError('Transport is not IDLE')
|
throw new MtcuteError('Transport is not IDLE')
|
||||||
|
@ -110,7 +110,7 @@ export abstract class BaseHttpProxyTcpTransport extends BaseTcpTransport {
|
||||||
if (this._proxy.password) {
|
if (this._proxy.password) {
|
||||||
auth += ':' + this._proxy.password
|
auth += ':' + this._proxy.password
|
||||||
}
|
}
|
||||||
headers['Proxy-Authorization'] = 'Basic ' + base64Encode(utf8EncodeToBuffer(auth))
|
headers['Proxy-Authorization'] = 'Basic ' + this._platform.base64Encode(this._platform.utf8Encode(auth))
|
||||||
}
|
}
|
||||||
headers['Proxy-Connection'] = 'Keep-Alive'
|
headers['Proxy-Connection'] = 'Keep-Alive'
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mtcute/core": "workspace:^"
|
"@mtcute/node": "workspace:^"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/* eslint-disable no-restricted-globals */
|
/* eslint-disable no-restricted-globals */
|
||||||
import { IPacketCodec, WrappedCodec } from '@mtcute/core'
|
import { IPacketCodec, WrappedCodec } from '@mtcute/node'
|
||||||
import { bigIntModInv, bigIntModPow, bigIntToBuffer, bufferToBigInt, ICryptoProvider } from '@mtcute/core/utils.js'
|
import { bigIntModInv, bigIntModPow, bigIntToBuffer, bufferToBigInt, ICryptoProvider } from '@mtcute/node/utils.js'
|
||||||
|
|
||||||
const MAX_TLS_PACKET_LENGTH = 2878
|
const MAX_TLS_PACKET_LENGTH = 2878
|
||||||
const TLS_FIRST_PREFIX = Buffer.from('140303000101', 'hex')
|
const TLS_FIRST_PREFIX = Buffer.from('140303000101', 'hex')
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import { connect } from 'net'
|
import { connect } from 'net'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
BaseTcpTransport,
|
||||||
IntermediatePacketCodec,
|
IntermediatePacketCodec,
|
||||||
IPacketCodec,
|
IPacketCodec,
|
||||||
MtcuteError,
|
MtcuteError,
|
||||||
|
@ -13,9 +14,8 @@ import {
|
||||||
PaddedIntermediatePacketCodec,
|
PaddedIntermediatePacketCodec,
|
||||||
tl,
|
tl,
|
||||||
TransportState,
|
TransportState,
|
||||||
} from '@mtcute/core'
|
} from '@mtcute/node'
|
||||||
import { BaseTcpTransport } from '@mtcute/core/src/network/transports/tcp.js'
|
import { buffersEqual } from '@mtcute/node/utils.js'
|
||||||
import { buffersEqual } from '@mtcute/core/utils.js'
|
|
||||||
|
|
||||||
import { FakeTlsPacketCodec, generateFakeTlsHeader } from './fake-tls.js'
|
import { FakeTlsPacketCodec, generateFakeTlsHeader } from './fake-tls.js'
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mtcute/core": "workspace:^"
|
"@mtcute/node": "workspace:^"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,16 @@
|
||||||
"description": "Meta-package for Node JS",
|
"description": "Meta-package for Node JS",
|
||||||
"author": "Alina Sireneva <alina@tei.su>",
|
"author": "Alina Sireneva <alina@tei.su>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "index.ts",
|
"main": "src/index.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"docs": "typedoc",
|
"docs": "typedoc",
|
||||||
"build": "pnpm run -w build-package node"
|
"build": "pnpm run -w build-package node"
|
||||||
},
|
},
|
||||||
|
"exports": {
|
||||||
|
".": "./src/index.ts",
|
||||||
|
"./utils.js": "./src/utils.ts"
|
||||||
|
},
|
||||||
"distOnlyFields": {
|
"distOnlyFields": {
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
|
@ -25,8 +29,12 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mtcute/core": "workspace:^",
|
"@mtcute/core": "workspace:^",
|
||||||
|
"@mtcute/wasm": "workspace:^",
|
||||||
"@mtcute/sqlite": "workspace:^",
|
"@mtcute/sqlite": "workspace:^",
|
||||||
"@mtcute/markdown-parser": "workspace:^",
|
"@mtcute/markdown-parser": "workspace:^",
|
||||||
"@mtcute/html-parser": "workspace:^"
|
"@mtcute/html-parser": "workspace:^"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@mtcute/test": "workspace:^"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
import { createRequire } from 'module'
|
import { createRequire } from 'module'
|
||||||
import { createInterface, Interface as RlInterface } from 'readline'
|
import { createInterface, Interface as RlInterface } from 'readline'
|
||||||
|
|
||||||
import { TelegramClient, TelegramClientOptions, User } from '@mtcute/core'
|
import { FileDownloadLocation, FileDownloadParameters, User } from '@mtcute/core'
|
||||||
|
import { TelegramClient as TelegramClientBase, TelegramClientOptions } from '@mtcute/core/client.js'
|
||||||
|
import { setPlatform } from '@mtcute/core/platform.js'
|
||||||
import { SqliteStorage } from '@mtcute/sqlite'
|
import { SqliteStorage } from '@mtcute/sqlite'
|
||||||
|
|
||||||
export * from '@mtcute/core'
|
import { downloadToFile } from './methods/download-file.js'
|
||||||
export * from '@mtcute/html-parser'
|
import { uploadFile } from './methods/upload-file.js'
|
||||||
export * from '@mtcute/markdown-parser'
|
import { NodePlatform } from './platform.js'
|
||||||
export { SqliteStorage }
|
import { NodeCryptoProvider } from './utils/crypto.js'
|
||||||
|
import { TcpTransport } from './utils/tcp.js'
|
||||||
|
|
||||||
|
export type { TelegramClientOptions }
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
let nativeCrypto: any
|
let nativeCrypto: any
|
||||||
|
@ -21,14 +26,12 @@ try {
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tiny wrapper over {@link TelegramClient} for usage inside Node JS.
|
* Telegram client for use in Node.js
|
||||||
*
|
|
||||||
* This class automatically manages native
|
|
||||||
* crypto addon and defaults to SQLite session (unlike `TelegarmClient`,
|
|
||||||
* which defaults to a JSON file on Node).
|
|
||||||
*/
|
*/
|
||||||
export class NodeTelegramClient extends TelegramClient {
|
export class TelegramClient extends TelegramClientBase {
|
||||||
constructor(opts: TelegramClientOptions) {
|
constructor(opts: TelegramClientOptions) {
|
||||||
|
setPlatform(new NodePlatform())
|
||||||
|
|
||||||
if ('client' in opts) {
|
if ('client' in opts) {
|
||||||
super(opts)
|
super(opts)
|
||||||
|
|
||||||
|
@ -37,7 +40,8 @@ export class NodeTelegramClient extends TelegramClient {
|
||||||
|
|
||||||
super({
|
super({
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
crypto: nativeCrypto ? () => new nativeCrypto() : undefined,
|
crypto: nativeCrypto ? new nativeCrypto() : new NodeCryptoProvider(),
|
||||||
|
transport: () => new TcpTransport(),
|
||||||
...opts,
|
...opts,
|
||||||
storage:
|
storage:
|
||||||
typeof opts.storage === 'string' ?
|
typeof opts.storage === 'string' ?
|
||||||
|
@ -74,7 +78,7 @@ export class NodeTelegramClient extends TelegramClient {
|
||||||
return super.close()
|
return super.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
start(params: Parameters<TelegramClient['start']>[0] = {}): Promise<User> {
|
start(params: Parameters<TelegramClientBase['start']>[0] = {}): Promise<User> {
|
||||||
if (!params.botToken) {
|
if (!params.botToken) {
|
||||||
if (!params.phone) params.phone = () => this.input('phone > ')
|
if (!params.phone) params.phone = () => this.input('phone > ')
|
||||||
if (!params.code) params.code = () => this.input('code > ')
|
if (!params.code) params.code = () => this.input('code > ')
|
||||||
|
@ -107,4 +111,16 @@ export class NodeTelegramClient extends TelegramClient {
|
||||||
.then(then)
|
.then(then)
|
||||||
.catch((err) => this.emitError(err))
|
.catch((err) => this.emitError(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
downloadToFile(
|
||||||
|
filename: string,
|
||||||
|
location: FileDownloadLocation,
|
||||||
|
params?: FileDownloadParameters | undefined,
|
||||||
|
): Promise<void> {
|
||||||
|
return downloadToFile(this, filename, location, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadFile(params: Parameters<typeof uploadFile>[1]) {
|
||||||
|
return uploadFile(this, params)
|
||||||
|
}
|
||||||
}
|
}
|
8
packages/node/src/index.ts
Normal file
8
packages/node/src/index.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
export * from './client.js'
|
||||||
|
export * from './platform.js'
|
||||||
|
export * from './utils/tcp.js'
|
||||||
|
export * from './utils/crypto.js'
|
||||||
|
export * from '@mtcute/core'
|
||||||
|
export * from '@mtcute/html-parser'
|
||||||
|
export * from '@mtcute/markdown-parser'
|
||||||
|
export * from '@mtcute/sqlite'
|
5
packages/node/src/methods.ts
Normal file
5
packages/node/src/methods.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/* eslint-disable import/export, simple-import-sort/exports */
|
||||||
|
export * from '@mtcute/core/methods.js'
|
||||||
|
|
||||||
|
export { downloadToFile } from './methods/download-file.js'
|
||||||
|
export { uploadFile } from './methods/upload-file.js'
|
40
packages/node/src/methods/download-file.ts
Normal file
40
packages/node/src/methods/download-file.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { createWriteStream, rmSync } from 'fs'
|
||||||
|
import { writeFile } from 'fs/promises'
|
||||||
|
|
||||||
|
import { FileDownloadLocation, FileDownloadParameters, FileLocation, ITelegramClient } from '@mtcute/core'
|
||||||
|
import { downloadAsIterable } from '@mtcute/core/methods.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download a remote file to a local file (only for NodeJS).
|
||||||
|
* Promise will resolve once the download is complete.
|
||||||
|
*
|
||||||
|
* @param filename Local file name to which the remote file will be downloaded
|
||||||
|
* @param params File download parameters
|
||||||
|
*/
|
||||||
|
export async function downloadToFile(
|
||||||
|
client: ITelegramClient,
|
||||||
|
filename: string,
|
||||||
|
location: FileDownloadLocation,
|
||||||
|
params?: FileDownloadParameters,
|
||||||
|
): Promise<void> {
|
||||||
|
if (location instanceof FileLocation && ArrayBuffer.isView(location.location)) {
|
||||||
|
// early return for inline files
|
||||||
|
await writeFile(filename, location.location)
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = createWriteStream(filename)
|
||||||
|
|
||||||
|
if (params?.abortSignal) {
|
||||||
|
params.abortSignal.addEventListener('abort', () => {
|
||||||
|
client.log.debug('aborting file download %s - cleaning up', filename)
|
||||||
|
output.destroy()
|
||||||
|
rmSync(filename)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const chunk of downloadAsIterable(client, location, params)) {
|
||||||
|
output.write(chunk)
|
||||||
|
}
|
||||||
|
|
||||||
|
output.end()
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue