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