chore!: started extracting platform-specific stuff into separate packages

This commit is contained in:
alina 🌸 2024-02-28 00:33:23 +03:00
parent ceb606a347
commit a2739b678c
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
160 changed files with 1452 additions and 772 deletions

View file

@ -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',
}, },

View file

@ -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"
} }
} }
}, },

View file

@ -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) })

View file

@ -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)
} }

View file

@ -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'

View file

@ -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'

View file

@ -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

View file

@ -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

View file

@ -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,
}, },

View file

@ -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()
}

View file

@ -1,5 +0,0 @@
import { MtUnsupportedError } from '../../../types/errors.js'
export function downloadToFile() {
throw new MtUnsupportedError('Downloading to file is only supported in NodeJS')
}

View file

@ -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)

View file

@ -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 {

View file

@ -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!')
} }

View file

@ -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,
} }
} }

View file

@ -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,
}) })

View file

@ -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: {

View file

@ -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

View file

@ -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)
} }
/** /**

View file

@ -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')

View file

@ -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)
}) })
}) })

View file

@ -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' +

View file

@ -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">' +

View file

@ -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) {

View file

@ -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)
} }

View file

@ -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)')
}

View file

@ -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!')
}

View file

@ -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]

View file

@ -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,

View file

@ -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]
} }

View file

@ -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',
) )

View file

@ -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

View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -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')
}) })
}) })

View file

@ -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']))
}) })

View file

@ -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 [

View 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
}

View file

@ -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'

View file

@ -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()

View file

@ -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'

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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))
}) })
} }

View file

@ -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(

View file

@ -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
} }

View file

@ -55,5 +55,3 @@ export abstract class BaseCryptoProvider {
return buf return buf
} }
} }
export type CryptoProviderFactory = () => ICryptoProvider

View file

@ -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)
} }

View file

@ -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

View file

@ -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', () => {

View file

@ -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,
} }

View file

@ -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)

View file

@ -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',
) )
}) })

View file

@ -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')
}) })
}) })

View file

@ -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)

View file

@ -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')
}) })
}) })

View file

@ -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)

View file

@ -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',

View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -1,4 +0,0 @@
import { NodeCryptoProvider } from '../crypto/node.js'
/** @internal */
export const _defaultCryptoProviderFactory = () => new NodeCryptoProvider()

View file

@ -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 })
}

View file

@ -1,4 +0,0 @@
import { TcpTransport } from '../../network/transports/tcp.js'
/** @internal */
export const _defaultTransportFactory = () => new TcpTransport()

View file

@ -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()

View file

@ -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:^"

View file

@ -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'

View file

@ -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(),
) )

View file

@ -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

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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 {

View file

@ -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 {

View file

@ -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:^"
} }
} }

View file

@ -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'),

View file

@ -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)})`)
} }

View file

@ -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', () => {

View file

@ -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)
} }

View file

@ -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)
} }

View file

@ -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')
}) })
}) })

View file

@ -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'

View file

@ -20,6 +20,6 @@
} }
}, },
"dependencies": { "dependencies": {
"@mtcute/core": "workspace:^" "@mtcute/node": "workspace:^"
} }
} }

View file

@ -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')

View file

@ -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'

View file

@ -20,6 +20,6 @@
} }
}, },
"dependencies": { "dependencies": {
"@mtcute/core": "workspace:^" "@mtcute/node": "workspace:^"
} }
} }

View file

@ -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:^"
} }
} }

View file

@ -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)
}
} }

View 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'

View 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'

View 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