diff --git a/package-lock.json b/package-lock.json index 888382acd..1044544b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@tauri-apps/plugin-shell": "^2.3.1", "@tauri-apps/plugin-updater": "^2.9.0", "async-mutex": "^0.5.0", - "big-integer": "github:painor/BigInteger.js", + "browserslist-config-baseline": "^0.5.0", "emoji-data-ios": "git+https://github.com/korenskoy/emoji-data-ios#443f1c9d7b16a82e7ee53f7f226d7d9a9920a105", "idb-keyval": "^6.2.2", "lowlight": "^3.3.0", @@ -7330,14 +7330,6 @@ "dev": true, "license": "MIT" }, - "node_modules/big-integer": { - "version": "1.7.0", - "resolved": "git+ssh://git@github.com/painor/BigInteger.js.git#4e2a4f1c1e4bd1d7c5d65e08aab51ac9ec939601", - "license": "Unlicense", - "engines": { - "node": ">=0.6" - } - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", diff --git a/package.json b/package.json index 94f4d8827..ecbb4e364 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "@tauri-apps/plugin-shell": "^2.3.1", "@tauri-apps/plugin-updater": "^2.9.0", "async-mutex": "^0.5.0", - "big-integer": "github:painor/BigInteger.js", + "browserslist-config-baseline": "^0.5.0", "emoji-data-ios": "git+https://github.com/korenskoy/emoji-data-ios#443f1c9d7b16a82e7ee53f7f226d7d9a9920a105", "idb-keyval": "^6.2.2", "lowlight": "^3.3.0", diff --git a/src/api/gramjs/apiBuilders/appConfig.ts b/src/api/gramjs/apiBuilders/appConfig.ts index 4f6933a0b..c41f55135 100644 --- a/src/api/gramjs/apiBuilders/appConfig.ts +++ b/src/api/gramjs/apiBuilders/appConfig.ts @@ -1,4 +1,3 @@ -import BigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; import type { ApiAppConfig, ApiLimitType, ApiPremiumSection } from '../../types'; @@ -131,7 +130,7 @@ function buildEmojiSounds(appConfig: GramJsAppConfig) { dcId: 1, mimeType: 'audio/ogg', fileReference: Buffer.alloc(0), - size: BigInt(0), + size: 0n, } as GramJs.Document); acc[key] = l.id; diff --git a/src/api/gramjs/apiBuilders/bots.ts b/src/api/gramjs/apiBuilders/bots.ts index 9df28b843..be32edfb9 100644 --- a/src/api/gramjs/apiBuilders/bots.ts +++ b/src/api/gramjs/apiBuilders/bots.ts @@ -24,6 +24,7 @@ import type { import { numberToHexColor } from '../../../util/colors'; import { pick } from '../../../util/iteratees'; +import { toJSNumber } from '../../../util/numbers'; import { addDocumentToLocalDb } from '../helpers/localDb'; import { serializeBytes } from '../helpers/misc'; import { buildApiMessageEntity, buildApiPhoto } from './common'; @@ -252,7 +253,7 @@ export function buildBotInlineMessage( description: sendMessage.description, photo: buildApiWebDocument(sendMessage.photo), currency: sendMessage.currency, - amount: sendMessage.totalAmount.toJSNumber(), + amount: toJSNumber(sendMessage.totalAmount), }; } diff --git a/src/api/gramjs/apiBuilders/chats.ts b/src/api/gramjs/apiBuilders/chats.ts index ec0938d10..56ee53bcd 100644 --- a/src/api/gramjs/apiBuilders/chats.ts +++ b/src/api/gramjs/apiBuilders/chats.ts @@ -24,6 +24,7 @@ import type { } from '../../types'; import { pickTruthy } from '../../../util/iteratees'; +import { toJSNumber } from '../../../util/numbers'; import { getServerTimeOffset } from '../../../util/serverTime'; import { addPhotoToLocalDb, addUserToLocalDb } from '../helpers/localDb'; import { serializeBytes } from '../helpers/misc'; @@ -113,7 +114,8 @@ function buildApiChatFieldsFromPeerEntity( isJoinRequest: channel?.joinRequest, isForum: channel?.forum, isMonoforum: channel?.monoforum, - linkedMonoforumId: channel?.linkedMonoforumId && buildApiPeerId(channel.linkedMonoforumId, 'channel'), + linkedMonoforumId: channel?.linkedMonoforumId !== undefined + ? buildApiPeerId(channel.linkedMonoforumId, 'channel') : undefined, areChannelMessagesAllowed: channel?.broadcastMessagesAllowed, areStoriesHidden, maxStoryId, @@ -123,7 +125,7 @@ function buildApiChatFieldsFromPeerEntity( botVerificationIconId, hasGeo: channel?.hasGeo, subscriptionUntil: channel?.subscriptionUntilDate, - paidMessagesStars: paidMessagesStars?.toJSNumber(), + paidMessagesStars: toJSNumber(paidMessagesStars), level: channel?.level, hasAutoTranslation: channel?.autotranslation, withForumTabs: channel?.forumTabs, @@ -713,7 +715,7 @@ export function buildApiStarsSubscriptionPricing( ): ApiStarsSubscriptionPricing { return { period: pricing.period, - amount: pricing.amount.toJSNumber(), + amount: toJSNumber(pricing.amount), }; } diff --git a/src/api/gramjs/apiBuilders/gifts.ts b/src/api/gramjs/apiBuilders/gifts.ts index 349b8d3cd..383d848b6 100644 --- a/src/api/gramjs/apiBuilders/gifts.ts +++ b/src/api/gramjs/apiBuilders/gifts.ts @@ -1,4 +1,3 @@ -import bigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; import type { @@ -14,6 +13,7 @@ import type { } from '../../types'; import { numberToHexColor } from '../../../util/colors'; +import { toJSNumber } from '../../../util/numbers'; import { buildApiChatFromPreview } from '../apiBuilders/chats'; import { addDocumentToLocalDb } from '../helpers/localDb'; import { buildApiFormattedText } from './common'; @@ -47,7 +47,7 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift { requirePremium, resaleTonOnly, valueCurrency, - valueAmount: valueAmount?.toJSNumber(), + valueAmount: toJSNumber(valueAmount), regularGiftId: giftId.toString(), }; } @@ -67,19 +67,19 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift { id: id.toString(), isLimited: limited, sticker, - stars: stars.toJSNumber(), + stars: toJSNumber(stars), availabilityRemains, availabilityTotal, - starsToConvert: convertStars.toJSNumber(), + starsToConvert: toJSNumber(convertStars), firstSaleDate, lastSaleDate, isSoldOut: soldOut, isBirthday: birthday, - upgradeStars: upgradeStars?.toJSNumber(), + upgradeStars: upgradeStars !== undefined ? toJSNumber(upgradeStars) : undefined, title, - resellMinStars: resellMinStars?.toJSNumber(), + resellMinStars: resellMinStars !== undefined ? toJSNumber(resellMinStars) : undefined, releasedByPeerId: releasedBy && getApiChatIdFromMtpPeer(releasedBy), - availabilityResale: availabilityResale?.toJSNumber(), + availabilityResale: availabilityResale !== undefined ? toJSNumber(availabilityResale) : undefined, requirePremium, limitedPerUser, perUserTotal, @@ -168,15 +168,15 @@ export function buildApiSavedStarGift(userStarGift: GramJs.SavedStarGift, peerId return { gift: buildApiStarGift(gift), date, - starsToConvert: convertStars?.toJSNumber(), + starsToConvert: toJSNumber(convertStars), fromId: fromId && getApiChatIdFromMtpPeer(fromId), message: message && buildApiFormattedText(message), messageId: msgId, isNameHidden: nameHidden, isUnsaved: unsaved, canUpgrade, - alreadyPaidUpgradeStars: upgradeStars?.toJSNumber(), - transferStars: transferStars?.toJSNumber(), + alreadyPaidUpgradeStars: toJSNumber(upgradeStars), + transferStars: toJSNumber(transferStars), inputGift, savedId: savedId?.toString(), canExportAt, @@ -279,16 +279,19 @@ GramJs.TypeStarGiftAttributeId[] { return attributes.map((attr) => { switch (attr.type) { case 'model': - return new GramJs.StarGiftAttributeIdModel({ documentId: bigInt(attr.documentId) }); + return new GramJs.StarGiftAttributeIdModel({ documentId: BigInt(attr.documentId) }); case 'pattern': - return new GramJs.StarGiftAttributeIdPattern({ documentId: bigInt(attr.documentId) }); + return new GramJs.StarGiftAttributeIdPattern({ documentId: BigInt(attr.documentId) }); case 'backdrop': return new GramJs.StarGiftAttributeIdBackdrop({ backdropId: attr.backdropId }); - default: - throw new Error(`Unknown attribute type: ${(attr as any).type}`); + default: { + // Exhaustive check + const _exhaustive: never = attr; + return _exhaustive; + } } }); } diff --git a/src/api/gramjs/apiBuilders/messageActions.ts b/src/api/gramjs/apiBuilders/messageActions.ts index 010df4f83..a2d96ff0e 100644 --- a/src/api/gramjs/apiBuilders/messageActions.ts +++ b/src/api/gramjs/apiBuilders/messageActions.ts @@ -3,6 +3,7 @@ import { Api as GramJs } from '../../../lib/gramjs'; import type { ApiPhoneCallDiscardReason } from '../../types'; import type { ApiMessageAction } from '../../types/messageActions'; +import { toJSNumber } from '../../../util/numbers'; import { buildApiBotApp } from './bots'; import { buildApiFormattedText, buildApiPhoto } from './common'; import { buildApiStarGift } from './gifts'; @@ -128,7 +129,7 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess isRecurringInit: recurringInit, isRecurringUsed: recurringUsed, currency, - totalAmount: totalAmount.toJSNumber(), + totalAmount: toJSNumber(totalAmount), invoiceSlug, subscriptionUntilDate, }; @@ -254,10 +255,10 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess mediaType: 'action', type: 'giftPremium', currency, - amount: amount.toJSNumber(), + amount: toJSNumber(amount), months, cryptoCurrency, - cryptoAmount: cryptoAmount?.toJSNumber(), + cryptoAmount: toJSNumber(cryptoAmount), message: message && buildApiFormattedText(message), }; } @@ -308,9 +309,9 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess months, slug, currency, - amount: amount?.toJSNumber(), + amount: toJSNumber(amount), cryptoCurrency, - cryptoAmount: cryptoAmount?.toJSNumber(), + cryptoAmount: toJSNumber(cryptoAmount), message: message && buildApiFormattedText(message), }; } @@ -319,7 +320,7 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess return { mediaType: 'action', type: 'giveawayLaunch', - stars: stars?.toJSNumber(), + stars: toJSNumber(stars), }; } if (action instanceof GramJs.MessageActionGiveawayResults) { @@ -341,7 +342,7 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess type: 'paymentRefunded', peerId: getApiChatIdFromMtpPeer(peer), currency, - totalAmount: totalAmount.toJSNumber(), + totalAmount: toJSNumber(totalAmount), }; } if (action instanceof GramJs.MessageActionGiftStars) { @@ -352,10 +353,10 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess mediaType: 'action', type: 'giftStars', currency, - amount: amount.toJSNumber(), - stars: stars.toJSNumber(), + amount: toJSNumber(amount), + stars: toJSNumber(stars), cryptoCurrency, - cryptoAmount: cryptoAmount?.toJSNumber(), + cryptoAmount: toJSNumber(cryptoAmount), transactionId, }; } @@ -367,9 +368,9 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess mediaType: 'action', type: 'giftTon', currency, - amount: amount.toJSNumber(), + amount: toJSNumber(amount), cryptoCurrency, - cryptoAmount: cryptoAmount.toJSNumber(), + cryptoAmount: toJSNumber(cryptoAmount), transactionId, }; } @@ -381,7 +382,7 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess mediaType: 'action', type: 'prizeStars', isUnclaimed: unclaimed, - stars: stars.toJSNumber(), + stars: toJSNumber(stars), transactionId, boostPeerId: getApiChatIdFromMtpPeer(boostPeer), giveawayMsgId, @@ -407,12 +408,12 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess canUpgrade, gift: starGift, message: message && buildApiFormattedText(message), - starsToConvert: convertStars?.toJSNumber(), + starsToConvert: toJSNumber(convertStars), upgradeMsgId, - alreadyPaidUpgradeStars: upgradeStars?.toJSNumber(), + alreadyPaidUpgradeStars: toJSNumber(upgradeStars), fromId: fromId && getApiChatIdFromMtpPeer(fromId), peerId: peer && getApiChatIdFromMtpPeer(peer), - savedId: savedId && buildApiPeerId(savedId, 'user'), + savedId: savedId !== undefined ? buildApiPeerId(savedId, 'user') : undefined, }; } if (action instanceof GramJs.MessageActionStarGiftUnique) { @@ -433,10 +434,10 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess isRefunded: refunded, gift: starGift, canExportAt, - transferStars: transferStars?.toJSNumber(), + transferStars: toJSNumber(transferStars), fromId: fromId && getApiChatIdFromMtpPeer(fromId), peerId: peer && getApiChatIdFromMtpPeer(peer), - savedId: savedId && buildApiPeerId(savedId, 'user'), + savedId: savedId !== undefined ? buildApiPeerId(savedId, 'user') : undefined, resaleAmount: resaleAmount ? buildApiCurrencyAmount(resaleAmount) : undefined, }; } @@ -448,7 +449,7 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess mediaType: 'action', type: 'paidMessagesPrice', isAllowedInChannel: broadcastMessagesAllowed, - stars: stars.toJSNumber(), + stars: toJSNumber(stars), }; } if (action instanceof GramJs.MessageActionPaidMessagesRefunded) { @@ -458,7 +459,7 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess return { mediaType: 'action', type: 'paidMessagesRefunded', - stars: stars.toJSNumber(), + stars: toJSNumber(stars), count, }; } diff --git a/src/api/gramjs/apiBuilders/messageContent.ts b/src/api/gramjs/apiBuilders/messageContent.ts index 82f3d1d26..a9a439358 100644 --- a/src/api/gramjs/apiBuilders/messageContent.ts +++ b/src/api/gramjs/apiBuilders/messageContent.ts @@ -35,6 +35,7 @@ import { SUPPORTED_PHOTO_CONTENT_TYPES, SUPPORTED_VIDEO_CONTENT_TYPES, VIDEO_WEB import { addTimestampEntities } from '../../../util/dates/timestamp'; import { generateWaveform } from '../../../util/generateWaveform'; import { pick } from '../../../util/iteratees'; +import { toJSNumber } from '../../../util/numbers'; import { addMediaToLocalDb, addStoryToLocalDb, addWebPageMediaToLocalDb, type MediaRepairContext, } from '../helpers/localDb'; @@ -260,7 +261,7 @@ export function buildVideoFromDocument(document: GramJs.Document, params?: { isRound, isGif: Boolean(gifAttr), thumbnail: buildApiThumbnailFromStripped(thumbs), - size: size.toJSNumber(), + size: toJSNumber(size), isSpoiler, timestamp, hasVideoPreview, @@ -300,7 +301,7 @@ export function buildAudioFromDocument(document: GramJs.Document): ApiAudio | un fileName: getFilenameFromDocument(document, 'audio'), title, performer, - size: size.toJSNumber(), + size: toJSNumber(size), }; } @@ -359,7 +360,7 @@ function buildAudio(media: GramJs.TypeMessageMedia): ApiAudio | undefined { id: String(media.document.id), fileName: getFilenameFromDocument(media.document, 'audio'), thumbnailSizes, - size: media.document.size.toJSNumber(), + size: toJSNumber(media.document.size), ...pick(media.document, ['mimeType']), ...pick(audioAttribute, ['duration', 'performer', 'title']), }; @@ -402,7 +403,7 @@ function buildVoice(media: GramJs.TypeMessageMedia): ApiVoice | undefined { return { mediaType: 'voice', id: String(media.document.id), - size: media.document.size.toJSNumber(), + size: toJSNumber(media.document.size), duration, waveform: waveform ? Array.from(waveform) : undefined, }; @@ -475,7 +476,7 @@ export function buildApiDocument(document: GramJs.TypeDocument): ApiDocument | u return { mediaType: 'document', id: String(id), - size: size.toJSNumber(), + size: toJSNumber(size), mimeType, timestamp: date, fileName: getFilenameFromDocument(document), @@ -644,7 +645,7 @@ function buildGiveaway(media: GramJs.MessageMediaGiveaway): ApiGiveaway | undefi mediaType: 'giveaway', channelIds, months, - stars: stars?.toJSNumber(), + stars: toJSNumber(stars), quantity, untilDate, countries: countriesIso2, @@ -770,7 +771,7 @@ export function buildMediaInvoice(media: GramJs.MessageMediaInvoice): ApiMediaIn description, photo: buildApiWebDocument(photo), receiptMessageId: receiptMsgId, - amount: totalAmount.toJSNumber(), + amount: toJSNumber(totalAmount), currency, isTest: test, extendedMedia: preview, @@ -932,7 +933,7 @@ function buildPaidMedia(media: GramJs.TypeMessageMedia): ApiPaidMedia | undefine if (isBought) { return { mediaType: 'paidMedia', - starsAmount: starsAmount.toJSNumber(), + starsAmount: toJSNumber(starsAmount), isBought, extendedMedia: buildBoughtMediaContent(extendedMedia)!, }; @@ -940,7 +941,7 @@ function buildPaidMedia(media: GramJs.TypeMessageMedia): ApiPaidMedia | undefine return { mediaType: 'paidMedia', - starsAmount: starsAmount.toJSNumber(), + starsAmount: toJSNumber(starsAmount), extendedMedia: extendedMedia .filter((paidMedia): paidMedia is GramJs.MessageExtendedMediaPreview => ( paidMedia instanceof GramJs.MessageExtendedMediaPreview diff --git a/src/api/gramjs/apiBuilders/messages.ts b/src/api/gramjs/apiBuilders/messages.ts index c8543c5db..399add47a 100644 --- a/src/api/gramjs/apiBuilders/messages.ts +++ b/src/api/gramjs/apiBuilders/messages.ts @@ -45,6 +45,7 @@ import { import { getEmojiOnlyCountForMessage } from '../../../global/helpers/getEmojiOnlyCountForMessage'; import { addTimestampEntities } from '../../../util/dates/timestamp'; import { omitUndefined, pick } from '../../../util/iteratees'; +import { toJSNumber } from '../../../util/numbers'; import { getServerTime, getServerTimeOffset } from '../../../util/serverTime'; import { interpolateArray } from '../../../util/waveform'; import { @@ -218,7 +219,7 @@ export function buildApiMessageWithChatId( mtpMessage.media instanceof GramJs.MessageMediaInvoice ? mtpMessage.media.receiptMsgId : undefined, ) || {}; const { mediaUnread: isMediaUnread, postAuthor } = mtpMessage; - const groupedId = mtpMessage.groupedId && String(mtpMessage.groupedId); + const groupedId = mtpMessage.groupedId !== undefined ? String(mtpMessage.groupedId) : undefined; const isInAlbum = Boolean(groupedId) && !(content.document || content.audio || content.sticker); const shouldHideKeyboardButtons = mtpMessage.replyMarkup instanceof GramJs.ReplyKeyboardHide; const isHideKeyboardSelective = mtpMessage.replyMarkup instanceof GramJs.ReplyKeyboardHide @@ -285,7 +286,7 @@ export function buildApiMessageWithChatId( isInvertedMedia, isVideoProcessingPending, reportDeliveryUntilDate: mtpMessage.reportDeliveryUntilDate, - paidMessageStars: mtpMessage.paidMessageStars?.toJSNumber(), + paidMessageStars: toJSNumber(mtpMessage.paidMessageStars), restrictionReasons, }; } @@ -835,6 +836,6 @@ export function buildApiSearchPostsFlood( totalDaily: searchFlood.totalDaily, remains: searchFlood.remains, waitTill: searchFlood.waitTill, - starsAmount: searchFlood.starsAmount.toJSNumber(), + starsAmount: toJSNumber(searchFlood.starsAmount), }; } diff --git a/src/api/gramjs/apiBuilders/misc.ts b/src/api/gramjs/apiBuilders/misc.ts index 29137a84b..aeb817de9 100644 --- a/src/api/gramjs/apiBuilders/misc.ts +++ b/src/api/gramjs/apiBuilders/misc.ts @@ -24,6 +24,7 @@ import { numberToHexColor } from '../../../util/colors'; import { buildCollectionByCallback, omit, omitUndefined, pick, } from '../../../util/iteratees'; +import { toJSNumber } from '../../../util/numbers'; import { addUserToLocalDb } from '../helpers/localDb'; import { omitVirtualClassFields } from './helpers'; import { buildApiDocument, buildMessageTextContent } from './messageContent'; @@ -362,9 +363,9 @@ export function buildApiCollectibleInfo(info: GramJs.fragment.TypeCollectibleInf } = info; return { - amount: amount.toJSNumber(), + amount: toJSNumber(amount), currency, - cryptoAmount: cryptoAmount.toJSNumber(), + cryptoAmount: toJSNumber(cryptoAmount), cryptoCurrency, purchaseDate, url, diff --git a/src/api/gramjs/apiBuilders/payments.ts b/src/api/gramjs/apiBuilders/payments.ts index 39be623a3..8618e7358 100644 --- a/src/api/gramjs/apiBuilders/payments.ts +++ b/src/api/gramjs/apiBuilders/payments.ts @@ -1,4 +1,3 @@ -import bigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; import type { @@ -31,6 +30,7 @@ import type { } from '../../types'; import { STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../../../config'; +import { toJSNumber } from '../../../util/numbers'; import { addWebDocumentToLocalDb } from '../helpers/localDb'; import { buildApiStarsSubscriptionPricing } from './chats'; import { buildApiMessageEntity } from './common'; @@ -45,15 +45,16 @@ export function buildShippingOptions(shippingOptions: GramJs.ShippingOption[] | return undefined; } - return Object.values(shippingOptions).map((option) => { + return shippingOptions.map((option) => { return { id: option.id, title: option.title, - amount: option.prices.reduce((ac, cur) => ac + cur.amount.toJSNumber(), 0), + + amount: option.prices.reduce((ac, cur) => ac + toJSNumber(cur.amount), 0), prices: option.prices.map(({ label, amount }) => { return { label, - amount: amount.toJSNumber(), + amount: toJSNumber(amount), }; }), }; @@ -79,7 +80,7 @@ export function buildApiReceipt(receipt: GramJs.payments.TypePaymentReceipt): Ap botId: buildApiPeerId(botId, 'user'), description, title, - totalAmount: -totalAmount.toJSNumber(), + totalAmount: -toJSNumber(totalAmount), transactionId, photo: buildApiWebDocument(photo), invoice: buildApiInvoice(invoice), @@ -110,7 +111,7 @@ export function buildApiReceipt(receipt: GramJs.payments.TypePaymentReceipt): Ap shippingPrices = shipping.prices.map(({ label, amount }) => { return { label, - amount: amount.toJSNumber(), + amount: toJSNumber(amount), }; }); shippingMethod = shipping.title; @@ -119,13 +120,13 @@ export function buildApiReceipt(receipt: GramJs.payments.TypePaymentReceipt): Ap return { type: 'regular', info: { shippingAddress, phone, name }, - totalAmount: totalAmount.toJSNumber(), + totalAmount: toJSNumber(totalAmount), currency, date, credentialsTitle, shippingPrices, shippingMethod, - tipAmount: tipAmount ? tipAmount.toJSNumber() : 0, + tipAmount: toJSNumber(tipAmount) || 0, title, description, botId: buildApiPeerId(botId, 'user'), @@ -240,10 +241,10 @@ export function buildApiInvoice(invoice: GramJs.Invoice): ApiInvoice { const mappedPrices: ApiLabeledPrice[] = prices.map(({ label, amount }) => ({ label, - amount: amount.toJSNumber(), + amount: toJSNumber(amount), })); - const totalAmount = prices.reduce((acc, cur) => acc.add(cur.amount), bigInt(0)).toJSNumber(); + const totalAmount = prices.reduce((acc, cur) => acc + toJSNumber(cur.amount), 0); return { totalAmount, @@ -252,8 +253,8 @@ export function buildApiInvoice(invoice: GramJs.Invoice): ApiInvoice { isRecurring: recurring, termsUrl, prices: mappedPrices, - maxTipAmount: maxTipAmount?.toJSNumber(), - suggestedTipAmounts: suggestedTipAmounts?.map((tip) => tip.toJSNumber()), + maxTipAmount: toJSNumber(maxTipAmount), + suggestedTipAmounts: suggestedTipAmounts?.map((tip) => toJSNumber(tip)), isEmailRequested: emailRequested, isEmailSentToProvider: emailToProvider, isNameRequested: nameRequested, @@ -288,7 +289,7 @@ function buildApiPremiumSubscriptionOption(option: GramJs.PremiumSubscriptionOpt isCurrent: current, canPurchaseUpgrade, currency, - amount: amount.toJSNumber(), + amount: toJSNumber(amount), botUrl, months, }; @@ -314,7 +315,7 @@ export function buildPrepaidGiveaway( return { type: 'starsGiveaway', id: interaction.id.toString(), - stars: interaction.stars.toJSNumber(), + stars: toJSNumber(interaction.stars), quantity: interaction.quantity, boosts: interaction.boosts, date: interaction.date, @@ -336,8 +337,8 @@ export function buildApiBoostsStatus(boostStatus: GramJs.premium.BoostsStatus): boostUrl, giftBoosts, nextLevelBoosts, - ...(premiumAudience && { premiumSubscribers: buildStatisticsPercentage(premiumAudience) }), - ...(prepaidGiveaways && { prepaidGiveaways: prepaidGiveaways.map((m) => buildPrepaidGiveaway(m)) }), + premiumSubscribers: premiumAudience && buildStatisticsPercentage(premiumAudience), + prepaidGiveaways: prepaidGiveaways?.map((m) => buildPrepaidGiveaway(m)), }; } @@ -352,12 +353,12 @@ export function buildApiBoost(boost: GramJs.Boost): ApiBoost { } = boost; return { - userId: userId && buildApiPeerId(userId, 'user'), + userId: userId !== undefined ? buildApiPeerId(userId, 'user') : undefined, multiplier, expires, isFromGiveaway: giveaway, isGift: gift, - stars: stars?.toJSNumber(), + stars: toJSNumber(stars), }; } @@ -390,7 +391,8 @@ export function buildApiGiveawayInfo(info: GramJs.payments.TypeGiveawayInfo): Ap type: 'active', startDate, isParticipating: participating, - adminDisallowedChatId: adminDisallowedChatId && buildApiPeerId(adminDisallowedChatId, 'channel'), + adminDisallowedChatId: adminDisallowedChatId !== undefined + ? buildApiPeerId(adminDisallowedChatId, 'channel') : undefined, disallowedCountry, joinedTooEarlyDate, isPreparingResults: preparingResults, @@ -416,7 +418,7 @@ export function buildApiGiveawayInfo(info: GramJs.payments.TypeGiveawayInfo): Ap giftCodeSlug, isRefunded: refunded, isWinner: winner, - starsPrize: starsPrize?.toJSNumber(), + starsPrize: toJSNumber(starsPrize), }; } } @@ -429,7 +431,7 @@ export function buildApiCheckedGiftCode(giftcode: GramJs.payments.TypeCheckedGif return { date, months, - toId: toId && buildApiPeerId(toId, 'user'), + toId: toId !== undefined ? buildApiPeerId(toId, 'user') : undefined, fromId: fromId && getApiChatIdFromMtpPeer(fromId), usedAt: usedDate, isFromGiveaway: viaGiveaway, @@ -443,7 +445,7 @@ export function buildApiPremiumGiftCodeOption(option: GramJs.PremiumGiftCodeOpti } = option; return { - amount: amount.toJSNumber(), + amount: toJSNumber(amount), currency, months, users, @@ -457,17 +459,17 @@ export function buildApiStarsGiftOptions(option: GramJs.StarsGiftOption): ApiSta return { isExtended: extended, - stars: stars.toJSNumber(), - amount: amount.toJSNumber(), + stars: toJSNumber(stars), + amount: toJSNumber(amount), currency, }; } -export function buildApiCurrencyAmount(amount: GramJs.TypeStarsAmount): ApiTypeCurrencyAmount | undefined { +export function buildApiCurrencyAmount(amount: GramJs.TypeStarsAmount): ApiTypeCurrencyAmount { if (amount instanceof GramJs.StarsAmount) { return { currency: STARS_CURRENCY_CODE, - amount: amount.amount.toJSNumber(), + amount: toJSNumber(amount.amount), nanos: amount.nanos, }; } @@ -475,11 +477,12 @@ export function buildApiCurrencyAmount(amount: GramJs.TypeStarsAmount): ApiTypeC if (amount instanceof GramJs.StarsTonAmount) { return { currency: TON_CURRENCY_CODE, - amount: amount.amount.toJSNumber(), + amount: toJSNumber(amount.amount), }; } - return undefined; + const _exhaustive: never = amount; + return _exhaustive; } export function buildApiStarsGiveawayWinnersOption( @@ -492,7 +495,7 @@ export function buildApiStarsGiveawayWinnersOption( return { isDefault, users, - perUserStars: perUserStars.toJSNumber(), + perUserStars: toJSNumber(perUserStars), }; } @@ -507,8 +510,8 @@ export function buildApiStarsGiveawayOptions(option: GramJs.StarsGiveawayOption) isExtended: extended, isDefault, yearlyBoosts, - stars: stars.toJSNumber(), - amount: amount.toJSNumber(), + stars: toJSNumber(stars), + amount: toJSNumber(amount), currency, winners: winnerList, }; @@ -625,9 +628,9 @@ export function buildApiStarTopupOption(option: GramJs.TypeStarsTopupOption): Ap } = option; return { - amount: amount.toJSNumber(), + amount: toJSNumber(amount), currency, - stars: stars.toJSNumber(), + stars: toJSNumber(stars), isExtended: extended, }; } @@ -644,14 +647,14 @@ export function buildApiUniqueStarGiftValueInfo( isLastSaleOnFragment: lastSaleOnFragment, isValueAverage: valueIsAverage, currency, - value: value.toJSNumber(), + value: toJSNumber(value), initialSaleDate, - initialSaleStars: initialSaleStars.toJSNumber(), - initialSalePrice: initialSalePrice.toJSNumber(), + initialSaleStars: toJSNumber(initialSaleStars), + initialSalePrice: toJSNumber(initialSalePrice), lastSaleDate, - lastSalePrice: lastSalePrice?.toJSNumber(), - floorPrice: floorPrice?.toJSNumber(), - averagePrice: averagePrice?.toJSNumber(), + lastSalePrice: toJSNumber(lastSalePrice), + floorPrice: toJSNumber(floorPrice), + averagePrice: toJSNumber(averagePrice), listedCount, fragmentListedCount, fragmentListedUrl, diff --git a/src/api/gramjs/apiBuilders/peers.ts b/src/api/gramjs/apiBuilders/peers.ts index 24dac32ca..26384b464 100644 --- a/src/api/gramjs/apiBuilders/peers.ts +++ b/src/api/gramjs/apiBuilders/peers.ts @@ -1,4 +1,3 @@ -import type BigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; import type { ApiEmojiStatusType, ApiPeerColor } from '../../types'; @@ -20,16 +19,16 @@ export function isMtpPeerChannel(peer: TypePeerOrInput): peer is GramJs.PeerChan return peer.hasOwnProperty('channelId'); } -export function buildApiPeerId(id: BigInt.BigInteger, type: 'user' | 'chat' | 'channel') { +export function buildApiPeerId(id: bigint, type: 'user' | 'chat' | 'channel') { if (type === 'user') { return id.toString(); } if (type === 'channel') { - return id.add(CHANNEL_ID_BASE).negate().toString(); + return ((id + CHANNEL_ID_BASE) * -1n).toString(); } - return id.negate().toString(); + return (id * -1n).toString(); } export function getApiChatIdFromMtpPeer(peer: TypePeerOrInput) { diff --git a/src/api/gramjs/apiBuilders/statistics.ts b/src/api/gramjs/apiBuilders/statistics.ts index 9a7f99a70..a3038257b 100644 --- a/src/api/gramjs/apiBuilders/statistics.ts +++ b/src/api/gramjs/apiBuilders/statistics.ts @@ -17,10 +17,9 @@ import type { } from '../../types'; import { buildApiUsernames, buildAvatarPhotoId } from './common'; +import { buildApiCurrencyAmount } from './payments'; import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers'; -const DECIMALS = 10 ** 9; - export function buildChannelStatistics(stats: GramJs.stats.BroadcastStats): ApiChannelStatistics { return { type: 'channel', @@ -275,16 +274,16 @@ function buildApiMessagePublicForward(message: GramJs.TypeMessage, chats: GramJs }; } -function buildChannelMonetizationBalances({ - currentBalance, - availableBalance, - overallRevenue, - withdrawalEnabled, -}: GramJs.StarsRevenueStatus): ChannelMonetizationBalances { +function buildChannelMonetizationBalances(revenueStatus: GramJs.StarsRevenueStatus): ChannelMonetizationBalances { + const currentBalance = buildApiCurrencyAmount(revenueStatus.currentBalance); + const availableBalance = buildApiCurrencyAmount(revenueStatus.availableBalance); + const overallRevenue = buildApiCurrencyAmount(revenueStatus.overallRevenue); + const withdrawalEnabled = revenueStatus.withdrawalEnabled; + return { - currentBalance: Number(currentBalance) / DECIMALS, - availableBalance: Number(availableBalance) / DECIMALS, - overallRevenue: Number(overallRevenue) / DECIMALS, + currentBalance, + availableBalance, + overallRevenue, isWithdrawalEnabled: withdrawalEnabled, }; } diff --git a/src/api/gramjs/apiBuilders/symbols.ts b/src/api/gramjs/apiBuilders/symbols.ts index c783d78da..6d8462418 100644 --- a/src/api/gramjs/apiBuilders/symbols.ts +++ b/src/api/gramjs/apiBuilders/symbols.ts @@ -115,7 +115,7 @@ export function buildStickerSet(set: GramJs.StickerSet): ApiStickerSet { const hasStaticThumb = thumbs?.some((thumb) => thumb.type === 's'); const hasAnimatedThumb = thumbs?.some((thumb) => thumb.type === 'a'); const hasVideoThumb = thumbs?.some((thumb) => thumb.type === 'v'); - const thumbCustomEmojiId = thumbDocumentId && String(thumbDocumentId); + const thumbCustomEmojiId = thumbDocumentId !== undefined ? String(thumbDocumentId) : undefined; const hasThumbnail = hasStaticThumb || hasAnimatedThumb || hasVideoThumb || Boolean(thumbCustomEmojiId); diff --git a/src/api/gramjs/apiBuilders/users.ts b/src/api/gramjs/apiBuilders/users.ts index 95a7d9595..4befffb21 100644 --- a/src/api/gramjs/apiBuilders/users.ts +++ b/src/api/gramjs/apiBuilders/users.ts @@ -10,6 +10,7 @@ import type { ApiUserType, } from '../../types'; +import { toJSNumber } from '../../../util/numbers'; import { buildApiBotInfo } from './bots'; import { buildApiBusinessIntro, buildApiBusinessLocation, buildApiBusinessWorkHours } from './business'; import { @@ -54,7 +55,8 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse businessLocation: businessLocation && buildApiBusinessLocation(businessLocation), businessWorkHours: businessWorkHours && buildApiBusinessWorkHours(businessWorkHours), businessIntro: businessIntro && buildApiBusinessIntro(businessIntro), - personalChannelId: personalChannelId && buildApiPeerId(personalChannelId, 'channel'), + personalChannelId: personalChannelId !== undefined + ? buildApiPeerId(personalChannelId, 'channel') : undefined, personalChannelMessageId: personalChannelMessage, botVerification: botVerification && buildApiBotVerification(botVerification), areAdsEnabled: sponsoredEnabled, @@ -64,7 +66,7 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse starsMyPendingRatingDate, isBotCanManageEmojiStatus: botCanManageEmojiStatus, hasScheduledMessages: hasScheduled, - paidMessagesStars: sendPaidMessagesStars?.toJSNumber(), + paidMessagesStars: toJSNumber(sendPaidMessagesStars), settings: buildApiPeerSettings(settings), }; } @@ -89,7 +91,7 @@ export function buildApiPeerSettings({ phoneCountry, nameChangeDate, photoChangeDate, - chargedPaidMessageStars: chargePaidMessageStars?.toJSNumber(), + chargedPaidMessageStars: toJSNumber(chargePaidMessageStars), }; } @@ -143,7 +145,7 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined { botVerificationIconId: botVerificationIcon?.toString(), color: mtpUser.color && buildApiPeerColor(mtpUser.color), profileColor: profileColor && buildApiPeerColor(profileColor), - paidMessagesStars: sendPaidMessagesStars?.toJSNumber(), + paidMessagesStars: toJSNumber(sendPaidMessagesStars), }; } @@ -193,8 +195,8 @@ export function buildApiBirthday(birthday: GramJs.TypeBirthday): ApiBirthday { export function buildApiStarsRating(starsRating: GramJs.StarsRating): ApiStarsRating { return { level: starsRating.level, - currentLevelStars: starsRating.currentLevelStars.toJSNumber(), - stars: starsRating.stars.toJSNumber(), - nextLevelStars: starsRating.nextLevelStars?.toJSNumber(), + currentLevelStars: toJSNumber(starsRating.currentLevelStars), + stars: toJSNumber(starsRating.stars), + nextLevelStars: toJSNumber(starsRating.nextLevelStars), }; } diff --git a/src/api/gramjs/gramjsBuilders/index.ts b/src/api/gramjs/gramjsBuilders/index.ts index 55a8b07a7..d1855ddab 100644 --- a/src/api/gramjs/gramjsBuilders/index.ts +++ b/src/api/gramjs/gramjsBuilders/index.ts @@ -1,6 +1,5 @@ -import BigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; -import { generateRandomBytes, readBigIntFromBuffer } from '../../../lib/gramjs/Helpers'; +import { generateRandomBigInt, generateRandomBytes, readBigIntFromBuffer } from '../../../lib/gramjs/Helpers'; import type { ApiBotApp, @@ -47,13 +46,13 @@ import localDb from '../localDb'; export const DEFAULT_PRIMITIVES = { INT: 0, - BIGINT: BigInt(0), + BIGINT: 0n, STRING: '', } as const; export function getEntityTypeById(peerId: string) { - const n = Number(peerId); - if (n > 0) { + const n = BigInt(peerId); + if (n > 0n) { return 'user'; } @@ -143,7 +142,7 @@ export function buildInputPaidReactionPrivacy(isPrivate?: boolean, peerId?: stri export function buildInputPeerFromLocalDb(chatOrUserId: string): GramJs.TypeInputPeer | undefined { const type = getEntityTypeById(chatOrUserId); - let accessHash: BigInt.BigInteger | undefined; + let accessHash: bigint | undefined; if (type === 'user') { accessHash = localDb.users[chatOrUserId]?.accessHash; @@ -207,7 +206,7 @@ export function buildInputMediaDocument(media: ApiSticker | ApiVideo) { return new GramJs.InputMediaDocument({ id: inputDocument }); } -export function buildInputPoll(pollParams: ApiNewPoll, randomId: BigInt.BigInteger) { +export function buildInputPoll(pollParams: ApiNewPoll, randomId: bigint) { const { summary, quiz } = pollParams; const poll = new GramJs.Poll({ @@ -359,10 +358,6 @@ export function buildInputStory(story: ApiStory | ApiStorySkipped) { }); } -export function generateRandomBigInt() { - return readBigIntFromBuffer(generateRandomBytes(8), true, true); -} - export function generateRandomTimestampedBigInt() { // 32 bits for timestamp, 32 bits are random const buffer = generateRandomBytes(8); @@ -372,10 +367,6 @@ export function generateRandomTimestampedBigInt() { return readBigIntFromBuffer(buffer, true, true); } -export function generateRandomInt() { - return readBigIntFromBuffer(generateRandomBytes(4), true, true).toJSNumber(); -} - export function buildMessageFromUpdate( id: number, chatId: string, @@ -470,7 +461,7 @@ export function buildInputContact({ lastName: string; }) { return new GramJs.InputPhoneContact({ - clientId: BigInt(1), + clientId: 1n, phone, firstName, lastName, @@ -597,17 +588,16 @@ export function buildInputThemeParams(params: ApiThemeParameters) { } export function buildMtpPeerId(id: string, type: 'user' | 'chat' | 'channel') { + const n = BigInt(id); if (type === 'user') { - return BigInt(id); + return n; } - const n = Number(id); - if (type === 'channel') { - return BigInt(-n - CHANNEL_ID_BASE); + return -n - CHANNEL_ID_BASE; } - return BigInt(n * -1); + return n * -1n; } export function buildInputGroupCall(groupCall: Partial) { diff --git a/src/api/gramjs/localDb.ts b/src/api/gramjs/localDb.ts index 4e2170e7e..de9521caa 100644 --- a/src/api/gramjs/localDb.ts +++ b/src/api/gramjs/localDb.ts @@ -1,4 +1,3 @@ -import BigInt from 'big-integer'; import { Api as GramJs } from '../../lib/gramjs'; import { DEBUG } from '../../config'; diff --git a/src/api/gramjs/methods/account.ts b/src/api/gramjs/methods/account.ts index fea9b586f..ae7f28f6c 100644 --- a/src/api/gramjs/methods/account.ts +++ b/src/api/gramjs/methods/account.ts @@ -1,4 +1,3 @@ -import BigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; import type { diff --git a/src/api/gramjs/methods/bots.ts b/src/api/gramjs/methods/bots.ts index a88bb44a9..a10a587c6 100644 --- a/src/api/gramjs/methods/bots.ts +++ b/src/api/gramjs/methods/bots.ts @@ -1,5 +1,5 @@ -import BigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; +import { generateRandomBigInt } from '../../../lib/gramjs/Helpers'; import type { ApiBotApp, @@ -33,7 +33,6 @@ import { buildInputThemeParams, buildInputUser, DEFAULT_PRIMITIVES, - generateRandomBigInt, } from '../gramjsBuilders'; import { addDocumentToLocalDb, diff --git a/src/api/gramjs/methods/calls.ts b/src/api/gramjs/methods/calls.ts index 59ad4382a..3eea93dc8 100644 --- a/src/api/gramjs/methods/calls.ts +++ b/src/api/gramjs/methods/calls.ts @@ -1,5 +1,5 @@ -import BigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; +import { generateRandomInt32 } from '../../../lib/gramjs/Helpers'; import type { JoinGroupCallPayload } from '../../../lib/secret-sauce'; import type { @@ -13,7 +13,7 @@ import { buildPhoneCall, } from '../apiBuilders/calls'; import { - buildInputGroupCall, buildInputPeer, buildInputPhoneCall, buildInputUser, DEFAULT_PRIMITIVES, generateRandomInt, + buildInputGroupCall, buildInputPeer, buildInputPhoneCall, buildInputUser, DEFAULT_PRIMITIVES, } from '../gramjsBuilders'; import { sendApiUpdate } from '../updates/apiUpdateEmitter'; import { invokeRequest, invokeRequestBeacon } from './client'; @@ -182,7 +182,7 @@ export async function createGroupCall({ }: { peer: ApiChat; }) { - const randomId = generateRandomInt(); + const randomId = generateRandomInt32(); const result = await invokeRequest(new GramJs.phone.CreateGroupCall({ peer: buildInputPeer(peer.id, peer.accessHash), randomId, @@ -284,7 +284,7 @@ export async function requestCall({ user: ApiUser; gAHash: number[]; isVideo?: boolean; }) { const result = await invokeRequest(new GramJs.phone.RequestCall({ - randomId: generateRandomInt(), + randomId: generateRandomInt32(), userId: buildInputUser(user.id, user.accessHash), gAHash: Buffer.from(gAHash), ...(isVideo && { video: true }), diff --git a/src/api/gramjs/methods/chats.ts b/src/api/gramjs/methods/chats.ts index 05a4fab8e..3421b86ca 100644 --- a/src/api/gramjs/methods/chats.ts +++ b/src/api/gramjs/methods/chats.ts @@ -1,6 +1,6 @@ -import BigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; import { RPCError } from '../../../lib/gramjs/errors'; +import { generateRandomBigInt } from '../../../lib/gramjs/Helpers'; import type { ChatListType } from '../../../types'; import type { @@ -71,7 +71,6 @@ import { buildInputUser, buildMtpMessageEntity, DEFAULT_PRIMITIVES, - generateRandomBigInt, getEntityTypeById, } from '../gramjsBuilders'; import { diff --git a/src/api/gramjs/methods/media.ts b/src/api/gramjs/methods/media.ts index 9ef257f61..8187208f3 100644 --- a/src/api/gramjs/methods/media.ts +++ b/src/api/gramjs/methods/media.ts @@ -1,4 +1,3 @@ -import bigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; import type { SizeType, TelegramClient } from '../../../lib/gramjs'; @@ -15,6 +14,7 @@ import { MEDIA_CACHE_NAME_AVATARS, } from '../../../config'; import * as cacheApi from '../../../util/cacheApi'; +import { toJSNumber } from '../../../util/numbers'; import { getEntityTypeById } from '../gramjsBuilders'; import localDb from '../localDb'; @@ -89,7 +89,7 @@ async function download( } = parsed; if (entityType === 'staticMap') { - const accessHash = bigInt(entityId); + const accessHash = BigInt(entityId); const parsedParams = new URLSearchParams(params); const long = Number(parsedParams.get('long')); const lat = Number(parsedParams.get('lat')); @@ -162,7 +162,7 @@ async function download( fullSize = entity.size; } else if (entity instanceof GramJs.Document) { mimeType = entity.mimeType; - fullSize = entity.size.toJSNumber(); + fullSize = toJSNumber(entity.size); } // Prevent HTML-in-video attacks diff --git a/src/api/gramjs/methods/messages.ts b/src/api/gramjs/methods/messages.ts index db0edaca8..6865023b6 100644 --- a/src/api/gramjs/methods/messages.ts +++ b/src/api/gramjs/methods/messages.ts @@ -1,6 +1,6 @@ -import BigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; import { RPCError } from '../../../lib/gramjs/errors'; +import { generateRandomBigInt } from '../../../lib/gramjs/Helpers'; import type { ForwardMessagesParams, @@ -98,7 +98,6 @@ import { buildPeer, buildSendMessageAction, DEFAULT_PRIMITIVES, - generateRandomBigInt, getEntityTypeById, } from '../gramjsBuilders'; import { @@ -1888,7 +1887,7 @@ export async function forwardApiMessages(params: ForwardMessagesParams) { const priceInStars = messagePriceInStars ? messagePriceInStars * messageIds.length : undefined; - const randomIds = messageIds.map(generateRandomBigInt); + const randomIds = messageIds.map(() => generateRandomBigInt()); try { const update = await invokeRequest(new GramJs.messages.ForwardMessages({ fromPeer: buildInputPeer(fromChat.id, fromChat.accessHash), @@ -2498,7 +2497,7 @@ export async function sendQuickReply({ if (!messages || messages instanceof GramJs.messages.MessagesNotModified) return; const ids = messages.messages.map((m) => m.id); - const randomIds = ids.map(generateRandomBigInt); + const randomIds = ids.map(() => generateRandomBigInt()); const result = await invokeRequest(new GramJs.messages.SendQuickReplyMessages({ peer: buildInputPeer(chat.id, chat.accessHash), diff --git a/src/api/gramjs/methods/payments.ts b/src/api/gramjs/methods/payments.ts index 7984e534f..6649f044f 100644 --- a/src/api/gramjs/methods/payments.ts +++ b/src/api/gramjs/methods/payments.ts @@ -1,4 +1,3 @@ -import BigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; import { RPCError } from '../../../lib/gramjs/errors'; diff --git a/src/api/gramjs/methods/phoneCallState.ts b/src/api/gramjs/methods/phoneCallState.ts index d287547d2..926ce375a 100644 --- a/src/api/gramjs/methods/phoneCallState.ts +++ b/src/api/gramjs/methods/phoneCallState.ts @@ -1,5 +1,3 @@ -import type bigInt from 'big-integer'; -import BigInt from 'big-integer'; import { AuthKey } from '../../../lib/gramjs/crypto/AuthKey'; import { Logger } from '../../../lib/gramjs/extensions'; import { @@ -20,13 +18,13 @@ class PhoneCallState { private seq = 0; - private gA?: bigInt.BigInteger; + private gA?: bigint; - private gB: any; + private gB?: bigint; - private p?: bigInt.BigInteger; + private p?: bigint; - private random?: bigInt.BigInteger; + private random?: bigint; private waitForState: Promise; @@ -55,8 +53,8 @@ class PhoneCallState { } acceptCall({ p, g, random }: DhConfig) { - const pLast = readBigIntFromBuffer(p, false); - const randomLast = readBigIntFromBuffer(random, false); + const pLast = readBigIntFromBuffer(Buffer.from(p), false); + const randomLast = readBigIntFromBuffer(Buffer.from(random), false); const gB = modExp(BigInt(g), randomLast, pLast); this.gB = gB; @@ -77,7 +75,7 @@ class PhoneCallState { this.gA = readBigIntFromBuffer(Buffer.from(gAOrB), false); } const authKey = modExp( - !this.isOutgoing ? this.gA : this.gB, + (!this.isOutgoing ? this.gA : this.gB)!, this.random, this.p, ); @@ -125,14 +123,14 @@ class PhoneCallState { // https://github.com/TelegramV/App/blob/ead52320975362139cabad18cf8346f98c349a22/src/js/MTProto/Calls/Internal.js#L72 function computeEmojiIndex(bytes: Uint8Array) { - return ((BigInt(bytes[0]).and(0x7F)).shiftLeft(56)) - .or((BigInt(bytes[1]).shiftLeft(48))) - .or((BigInt(bytes[2]).shiftLeft(40))) - .or((BigInt(bytes[3]).shiftLeft(32))) - .or((BigInt(bytes[4]).shiftLeft(24))) - .or((BigInt(bytes[5]).shiftLeft(16))) - .or((BigInt(bytes[6]).shiftLeft(8))) - .or((BigInt(bytes[7]))); + return ((BigInt(bytes[0]) & 0x7Fn) << 56n) + | ((BigInt(bytes[1]) << 48n)) + | ((BigInt(bytes[2]) << 40n)) + | ((BigInt(bytes[3]) << 32n)) + | ((BigInt(bytes[4]) << 24n)) + | ((BigInt(bytes[5]) << 16n)) + | ((BigInt(bytes[6]) << 8n)) + | ((BigInt(bytes[7]))); } async function generateEmojiFingerprint( @@ -144,7 +142,7 @@ async function generateEmojiFingerprint( const kPartSize = 8; for (let partOffset = 0; partOffset !== hash.byteLength; partOffset += kPartSize) { const value = computeEmojiIndex(hash.subarray(partOffset, partOffset + kPartSize)); - const index = value.modPow(1, emojiCount).toJSNumber(); + const index = Number(value % BigInt(emojiCount)); const offset = emojiOffsets[index]; const size = emojiOffsets[index + 1] - offset; result.push(String.fromCharCode(...emojiData.subarray(offset, offset + size))); diff --git a/src/api/gramjs/methods/reactions.ts b/src/api/gramjs/methods/reactions.ts index e5ae60f41..ace0aea1d 100644 --- a/src/api/gramjs/methods/reactions.ts +++ b/src/api/gramjs/methods/reactions.ts @@ -1,4 +1,3 @@ -import BigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; import type { diff --git a/src/api/gramjs/methods/settings.ts b/src/api/gramjs/methods/settings.ts index f02da81c7..7ed4b5684 100644 --- a/src/api/gramjs/methods/settings.ts +++ b/src/api/gramjs/methods/settings.ts @@ -1,4 +1,3 @@ -import BigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; import { RPCError } from '../../../lib/gramjs/errors'; @@ -23,6 +22,7 @@ import { UNMUTE_TIMESTAMP, } from '../../../config'; import { buildCollectionByKey } from '../../../util/iteratees'; +import { toJSNumber } from '../../../util/numbers'; import { BLOCKED_LIST_LIMIT } from '../../../limits'; import { buildAppConfig } from '../apiBuilders/appConfig'; import { buildApiPhoto, buildPrivacyRules } from '../apiBuilders/common'; @@ -686,7 +686,7 @@ export async function fetchGlobalPrivacySettings() { shouldArchiveAndMuteNewNonContact: Boolean(result.archiveAndMuteNewNoncontactPeers), shouldHideReadMarks: Boolean(result.hideReadMarks), shouldNewNonContactPeersRequirePremium: Boolean(result.newNoncontactPeersRequirePremium), - nonContactPeersPaidStars: Number(result.noncontactPeersPaidStars), + nonContactPeersPaidStars: toJSNumber(result.noncontactPeersPaidStars), shouldDisplayGiftsButton: Boolean(result.displayGiftsButton), disallowedGifts: result.disallowedGifts && buildApiDisallowedGiftsSettings(result.disallowedGifts), }; @@ -726,7 +726,7 @@ export async function updateGlobalPrivacySettings({ shouldArchiveAndMuteNewNonContact: Boolean(result.archiveAndMuteNewNoncontactPeers), shouldHideReadMarks: Boolean(result.hideReadMarks), shouldNewNonContactPeersRequirePremium: Boolean(result.newNoncontactPeersRequirePremium), - nonContactPeersPaidStars: Number(result.noncontactPeersPaidStars), + nonContactPeersPaidStars: toJSNumber(result.noncontactPeersPaidStars), shouldDisplayGiftsButton, disallowedGifts, }; diff --git a/src/api/gramjs/methods/stars.ts b/src/api/gramjs/methods/stars.ts index 820057a38..61e874765 100644 --- a/src/api/gramjs/methods/stars.ts +++ b/src/api/gramjs/methods/stars.ts @@ -1,4 +1,3 @@ -import bigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; import type { GiftProfileFilterOptions, ResaleGiftsFilterOptions } from '../../../types'; @@ -39,7 +38,7 @@ import { getPassword } from './twoFaSettings'; export async function fetchCheckCanSendGift({ giftId }: { giftId: string }) { const result = await invokeRequest(new GramJs.payments.CheckCanSendGift({ - giftId: bigInt(giftId), + giftId: BigInt(giftId), })); if (!result) { @@ -112,10 +111,10 @@ export async function fetchResaleGifts({ ]; const params: GetResaleStarGifts = { - giftId: bigInt(giftId), + giftId: BigInt(giftId), offset, limit, - attributesHash: attributesHash ? bigInt(attributesHash) : DEFAULT_PRIMITIVES.BIGINT, + attributesHash: attributesHash ? BigInt(attributesHash) : DEFAULT_PRIMITIVES.BIGINT, attributes: buildInputResaleGiftsAttributes(attributes), ...(filter && { sortByPrice: filter.sortType === 'byPrice' || undefined, @@ -200,7 +199,7 @@ export function convertStarGift({ })); } -export async function getStarsGiftOptions({ +export async function fetchStarsGiftOptions({ chat, }: { chat?: ApiChat; @@ -397,7 +396,7 @@ export async function fetchStarGiftUpgradePreview({ giftId: string; }) { const result = await invokeRequest(new GramJs.payments.GetStarGiftUpgradePreview({ - giftId: bigInt(giftId), + giftId: BigInt(giftId), })); if (!result) { @@ -527,7 +526,7 @@ export async function fetchStarGiftCollections({ }) { const result = await invokeRequest(new GramJs.payments.GetStarGiftCollections({ peer: buildInputPeer(peer.id, peer.accessHash), - hash: hash ? bigInt(hash) : DEFAULT_PRIMITIVES.BIGINT, + hash: hash ? BigInt(hash) : DEFAULT_PRIMITIVES.BIGINT, })); if (!result || result instanceof GramJs.payments.StarGiftCollectionsNotModified) { diff --git a/src/api/gramjs/methods/statistics.ts b/src/api/gramjs/methods/statistics.ts index 5ea859459..6fc863422 100644 --- a/src/api/gramjs/methods/statistics.ts +++ b/src/api/gramjs/methods/statistics.ts @@ -1,4 +1,3 @@ -import BigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; import type { diff --git a/src/api/gramjs/methods/symbols.ts b/src/api/gramjs/methods/symbols.ts index 44bad14ca..98b8cb830 100644 --- a/src/api/gramjs/methods/symbols.ts +++ b/src/api/gramjs/methods/symbols.ts @@ -1,4 +1,3 @@ -import BigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; import type { @@ -109,8 +108,14 @@ export async function fetchFeaturedStickers({ hash }: { hash?: string }) { }; } -export async function fetchFeaturedEmojiStickers() { - const result = await invokeRequest(new GramJs.messages.GetFeaturedEmojiStickers({ hash: BigInt(0) })); +export async function fetchFeaturedEmojiStickers({ + hash, +}: { + hash?: string; +}) { + const result = await invokeRequest(new GramJs.messages.GetFeaturedEmojiStickers({ + hash: hash ? BigInt(hash) : DEFAULT_PRIMITIVES.BIGINT, + })); if (!result || result instanceof GramJs.messages.FeaturedStickersNotModified) { return undefined; diff --git a/src/api/gramjs/methods/users.ts b/src/api/gramjs/methods/users.ts index dfb9b524e..7cad07924 100644 --- a/src/api/gramjs/methods/users.ts +++ b/src/api/gramjs/methods/users.ts @@ -1,9 +1,9 @@ -import BigInt from 'big-integer'; import { Api as GramJs } from '../../../lib/gramjs'; import type { ApiEmojiStatusType, ApiPeer, ApiUser, } from '../../types'; +import { toJSNumber } from '../../../util/numbers'; import { buildApiChatFromPreview } from '../apiBuilders/chats'; import { buildApiPhoto } from '../apiBuilders/common'; import { buildApiPeerId } from '../apiBuilders/peers'; @@ -88,7 +88,7 @@ export async function fetchFullUser({ }; } -export async function fetchCommonChats(user: ApiUser, maxId?: string) { +export async function fetchCommonChats({ user, maxId }: { user: ApiUser; maxId?: string }) { const result = await invokeRequest(new GramJs.messages.GetCommonChats({ userId: buildInputUser(user.id, user.accessHash), maxId: maxId @@ -112,12 +112,13 @@ export async function fetchPaidMessagesStarsAmount(user: ApiUser) { id: [buildInputUser(user.id, user.accessHash)], })); - if (!result) { + if (!result?.[0]) { return undefined; } + const requirement = result[0]; - if (result[0] instanceof GramJs.RequirementToContactPaidMessages) { - return result[0].starsAmount?.toJSNumber(); + if (requirement instanceof GramJs.RequirementToContactPaidMessages) { + return toJSNumber(requirement.starsAmount); } return undefined; @@ -149,7 +150,7 @@ export async function fetchTopUsers() { } export async function fetchContactList() { - const result = await invokeRequest(new GramJs.contacts.GetContacts({ hash: BigInt('0') })); + const result = await invokeRequest(new GramJs.contacts.GetContacts({ hash: DEFAULT_PRIMITIVES.BIGINT })); if (!result || result instanceof GramJs.contacts.ContactsNotModified) { return undefined; } @@ -224,7 +225,7 @@ export function updateContact({ firstName, lastName, phone: phoneNumber, - ...(shouldSharePhoneNumber && { addPhonePrivacyException: shouldSharePhoneNumber }), + addPhonePrivacyException: shouldSharePhoneNumber || undefined, }), { shouldReturnTrue: true, }); @@ -273,7 +274,7 @@ export async function fetchPaidMessagesRevenue({ user }: { userId: buildInputUser(user.id, user.accessHash), })); if (!result) return undefined; - return result.starsAmount.toJSNumber(); + return toJSNumber(result.starsAmount); } export async function fetchProfilePhotos({ @@ -294,7 +295,7 @@ export async function fetchProfilePhotos({ userId: buildInputUser(id, accessHash), limit, offset, - maxId: BigInt('0'), + maxId: DEFAULT_PRIMITIVES.BIGINT, })); if (!result) { diff --git a/src/api/gramjs/updates/UpdatePts.ts b/src/api/gramjs/updates/UpdatePts.ts index 8e1f42c03..0a9bcb1d0 100644 --- a/src/api/gramjs/updates/UpdatePts.ts +++ b/src/api/gramjs/updates/UpdatePts.ts @@ -1,15 +1,13 @@ -import type { BigInteger } from 'big-integer'; - export class LocalUpdatePts { constructor(public pts: number, public ptsCount: number) {} } export class LocalUpdateChannelPts { - constructor(public channelId: BigInteger, public pts: number, public ptsCount: number) {} + constructor(public channelId: bigint, public pts: number, public ptsCount: number) {} } export type UpdatePts = LocalUpdatePts | LocalUpdateChannelPts; -export function buildLocalUpdatePts(pts: number, ptsCount: number, channelId?: BigInteger) { +export function buildLocalUpdatePts(pts: number, ptsCount: number, channelId?: bigint) { return channelId ? new LocalUpdateChannelPts(channelId, pts, ptsCount) : new LocalUpdatePts(pts, ptsCount); } diff --git a/src/api/types/statistics.ts b/src/api/types/statistics.ts index 80ae47566..2fb63992d 100644 --- a/src/api/types/statistics.ts +++ b/src/api/types/statistics.ts @@ -1,5 +1,6 @@ import type { ApiChat } from './chats'; import type { ApiTypePrepaidGiveaway } from './payments'; +import type { ApiTypeCurrencyAmount } from './stars'; export interface ApiChannelStatistics { type: 'channel'; @@ -153,8 +154,8 @@ export interface StatisticsStoryInteractionCounter { } export interface ChannelMonetizationBalances { - currentBalance: number; - availableBalance: number; - overallRevenue: number; + currentBalance: ApiTypeCurrencyAmount; + availableBalance: ApiTypeCurrencyAmount; + overallRevenue: ApiTypeCurrencyAmount; isWithdrawalEnabled?: boolean; } diff --git a/src/components/calls/phone/PhoneCall.tsx b/src/components/calls/phone/PhoneCall.tsx index 39131985d..852c98b04 100644 --- a/src/components/calls/phone/PhoneCall.tsx +++ b/src/components/calls/phone/PhoneCall.tsx @@ -20,6 +20,7 @@ import { } from '../../../util/browser/windowEnvironment'; import buildClassName from '../../../util/buildClassName'; import { formatMediaDuration } from '../../../util/dates/dateFormat'; +import { getServerTime } from '../../../util/serverTime'; import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets'; import renderText from '../../common/helpers/renderText'; @@ -215,7 +216,7 @@ const PhoneCall: FC = ({ setTimeout(stopFlipping, 250); }, [startFlipping, stopFlipping]); - const timeElapsed = phoneCall?.startDate && (Number(new Date()) / 1000 - phoneCall.startDate); + const timeElapsed = phoneCall?.startDate && (getServerTime() - phoneCall.startDate); useEffect(() => { if (phoneCall?.state === 'discarded') { diff --git a/src/components/right/statistics/MonetizationStatistics.tsx b/src/components/right/statistics/MonetizationStatistics.tsx index 22d8ee1b0..5276b53b8 100644 --- a/src/components/right/statistics/MonetizationStatistics.tsx +++ b/src/components/right/statistics/MonetizationStatistics.tsx @@ -7,6 +7,7 @@ import type { ApiChannelMonetizationStatistics } from '../../../api/types'; import { selectChat, selectChatFullInfo, selectTabState } from '../../../global/selectors'; import buildClassName from '../../../util/buildClassName'; +import { convertTonFromNanos } from '../../../util/formatCurrency'; import renderText from '../../common/helpers/renderText'; import { isGraph } from './helpers/isGraph'; @@ -144,9 +145,10 @@ const MonetizationStatistics = ({ }, [isReady, statistics, oldLang, chatId, dcId, forceUpdate]); function renderAvailableReward() { - const [integerTonPart, decimalTonPart] = availableBalance ? availableBalance.toFixed(4).split('.') : [0]; + const tonAmount = availableBalance ? convertTonFromNanos(availableBalance.amount) : 0; + const [integerTonPart, decimalTonPart] = tonAmount.toFixed(4).split('.'); const [integerUsdPart, decimalUsdPart] = availableBalance - && statistics?.usdRate ? (availableBalance * statistics.usdRate).toFixed(2).split('.') : [0]; + && statistics?.usdRate ? (tonAmount * statistics.usdRate).toFixed(2).split('.') : [0]; return (
diff --git a/src/components/right/statistics/StatisticsOverview.tsx b/src/components/right/statistics/StatisticsOverview.tsx index 7e1dbe36d..25f62276a 100644 --- a/src/components/right/statistics/StatisticsOverview.tsx +++ b/src/components/right/statistics/StatisticsOverview.tsx @@ -9,6 +9,7 @@ import type { import buildClassName from '../../../util/buildClassName'; import { formatFullDate } from '../../../util/dates/dateFormat'; +import { convertTonFromNanos } from '../../../util/formatCurrency'; import { formatInteger, formatIntegerCompact } from '../../../util/textFormat'; import useLang from '../../../hooks/useLang'; @@ -206,9 +207,21 @@ const StatisticsOverview: FC = ({ {isToncoin ? ( - {renderBalanceCell(balances?.availableBalance || 0, usdRate || 0, 'lng_channel_earn_available')} - {renderBalanceCell(balances?.currentBalance || 0, usdRate || 0, 'lng_channel_earn_reward')} - {renderBalanceCell(balances?.overallRevenue || 0, usdRate || 0, 'lng_channel_earn_total')} + {renderBalanceCell( + balances?.availableBalance ? convertTonFromNanos(balances.availableBalance.amount) : 0, + usdRate || 0, + 'lng_channel_earn_available', + )} + {renderBalanceCell( + balances?.currentBalance ? convertTonFromNanos(balances.currentBalance.amount) : 0, + usdRate || 0, + 'lng_channel_earn_reward', + )} + {renderBalanceCell( + balances?.overallRevenue ? convertTonFromNanos(balances.overallRevenue.amount) : 0, + usdRate || 0, + 'lng_channel_earn_total', + )} ) : schema.map((row) => ( diff --git a/src/config.ts b/src/config.ts index f43a7522f..d170bbc60 100644 --- a/src/config.ts +++ b/src/config.ts @@ -338,13 +338,13 @@ export const REPLIES_USER_ID = '1271266957'; // TODO For Test connection ID must export const VERIFICATION_CODES_USER_ID = '489000'; export const ANONYMOUS_USER_ID = '2666000'; export const RESTRICTED_EMOJI_SET_ID = '7173162320003080'; -export const CHANNEL_ID_BASE = 10 ** 12; +export const CHANNEL_ID_BASE = 10n ** 12n; export const DEFAULT_GIF_SEARCH_BOT_USERNAME = 'gif'; export const ALL_FOLDER_ID = 0; export const ARCHIVED_FOLDER_ID = 1; export const SAVED_FOLDER_ID = -1; export const FOLDER_TITLE_MAX_LENGTH = 12; -export const DELETED_COMMENTS_CHANNEL_ID = '-1000000000777'; +export const DELETED_COMMENTS_CHANNEL_ID = (-CHANNEL_ID_BASE - 777n).toString(); export const MAX_MEDIA_FILES_FOR_ALBUM = 10; export const MAX_ACTIVE_PINNED_CHATS = 5; export const SCHEDULED_WHEN_ONLINE = 0x7FFFFFFE; diff --git a/src/global/actions/api/payments.ts b/src/global/actions/api/payments.ts index 9688574c5..f3054c859 100644 --- a/src/global/actions/api/payments.ts +++ b/src/global/actions/api/payments.ts @@ -608,7 +608,12 @@ addActionHandler('openStarsGiftModal', async (global, actions, payload): Promise return; } - const starsGiftOptions = await callApi('getStarsGiftOptions', {}); + const chat = forUserId ? selectChat(global, forUserId) : undefined; + if (forUserId && !chat) return; + + const starsGiftOptions = await callApi('fetchStarsGiftOptions', { + chat, + }); global = getGlobal(); global = updateTabState(global, { diff --git a/src/global/actions/api/symbols.ts b/src/global/actions/api/symbols.ts index d29272e2f..047f67317 100644 --- a/src/global/actions/api/symbols.ts +++ b/src/global/actions/api/symbols.ts @@ -787,7 +787,7 @@ addActionHandler('clearCustomEmojiForEmoji', (global): ActionReturnType => { }); addActionHandler('loadFeaturedEmojiStickers', async (global): Promise => { - const featuredStickers = await callApi('fetchFeaturedEmojiStickers'); + const featuredStickers = await callApi('fetchFeaturedEmojiStickers', {}); if (!featuredStickers) { return; } diff --git a/src/global/actions/api/users.ts b/src/global/actions/api/users.ts index 40ea3e357..275b783bc 100644 --- a/src/global/actions/api/users.ts +++ b/src/global/actions/api/users.ts @@ -174,7 +174,10 @@ addActionHandler('loadCommonChats', async (global, actions, payload): Promise= bytesNumber) { - bigInt = bigInt.subtract(BigInt(2) - .pow(BigInt(bytesNumber * 8))); - } - return bigInt; -} +export function readBigIntFromBuffer(buffer: Buffer, little = true, signed = false): bigint { + const len = buffer.length; + if (len === 0) return 0n; -export function toSignedLittleBuffer(big: BigInt.BigInteger, number = 8) { - const bigNumber = BigInt(big); - const byteArray: number[] = []; - for (let i = 0; i < number; i++) { - byteArray[i] = bigNumber.shiftRight(8 * i) - .and(255) - .toJSNumber(); - } - - return Buffer.from(byteArray); -} - -export function readBufferFromBigInt(bigInt: BigInt.BigInteger, bytesNumber: number, little = true, signed = false) { - const bitLength = bigInt.bitLength().toJSNumber(); - - const bytes = Math.ceil(bitLength / 8); - if (bytesNumber < bytes) { - throw new Error('OverflowError: int too big to convert'); - } - if (!signed && bigInt.lesser(BigInt(0))) { - throw new Error('Cannot convert to unsigned'); - } - let below = false; - if (bigInt.lesser(BigInt(0))) { - below = true; - bigInt = bigInt.abs(); - } - - const hex = bigInt.toString(16).padStart(bytesNumber * 2, '0'); - let buffer = Buffer.from(hex, 'hex'); - - if (signed && below) { - buffer[buffer.length - 1] = 256 - buffer[buffer.length - 1]; - for (let i = 0; i < buffer.length - 1; i++) { - buffer[i] = 255 - buffer[i]; + // Hot path for longs + if (len === 8) { + if (signed) { + return little ? buffer.readBigInt64LE(0) : buffer.readBigInt64BE(0); + } else { + return little ? buffer.readBigUInt64LE(0) : buffer.readBigUInt64BE(0); } } + + // Parse unsigned value + let x = 0n; if (little) { - buffer = buffer.reverse(); + for (let i = len - 1; i >= 0; i--) x = (x << 8n) | BigInt(buffer[i]); + } else { + for (let i = 0; i < len; i++) x = (x << 8n) | BigInt(buffer[i]); + } + + // Apply two's-complement decode if signed and sign bit is set + if (signed) { + const signBit = 1n << BigInt(len * 8 - 1); + if ((x & signBit) !== 0n) x -= 1n << BigInt(len * 8); + } + return x; +} + +export function toSignedLittleBuffer(big: bigint, number = 8) { + const buffer = Buffer.allocUnsafe(number); + + // Use Buffer method for 8-byte buffers + if (number === 8) { + buffer.writeBigInt64LE(big); + return buffer; + } + + // For other sizes, extract bytes manually + for (let i = 0; i < number; i++) { + buffer[i] = Number((big >> BigInt(8 * i)) & 0xFFn); } return buffer; } +export function readBufferFromBigInt( + value: bigint, + bytesNumber: number, + little = true, + signed = false, +): Buffer { + if (!Number.isInteger(bytesNumber) || bytesNumber <= 0) { + throw new RangeError('bytesNumber must be a positive integer'); + } + if (!signed && value < 0n) { + throw new RangeError('Cannot convert negative to unsigned'); + } + + const bits = 8n * BigInt(bytesNumber); + const min = signed ? -(1n << (bits - 1n)) : 0n; + const max = signed ? (1n << (bits - 1n)) - 1n : (1n << bits) - 1n; + + if (value < min || value > max) { + throw new RangeError( + `Value ${value} does not fit in ${bytesNumber} ${signed ? 'signed' : 'unsigned'} bytes`, + ); + } + + // Two's complement encode if negative + let v = signed && value < 0n ? (1n << bits) + value : value; + + const buf = Buffer.allocUnsafe(bytesNumber); + if (little) { + for (let i = 0; i < bytesNumber; i++) { + buf[i] = Number(v & 0xFFn); + v >>= 8n; + } + } else { + for (let i = bytesNumber - 1; i >= 0; i--) { + buf[i] = Number(v & 0xFFn); + v >>= 8n; + } + } + return buf; +} + export function generateRandomLong(signed = true) { return readBigIntFromBuffer(generateRandomBytes(8), true, signed); } @@ -68,16 +95,24 @@ export function mod(n: number, m: number) { return ((n % m) + m) % m; } -export function bigIntMod(n: BigInt.BigInteger, m: BigInt.BigInteger) { - return ((n.remainder(m)).add(m)).remainder(m); +export function bigIntMod(n: bigint, m: bigint) { + return ((n % m) + m) % m; } export function generateRandomBytes(count: number) { return Buffer.from(randomBytes(count)); } +export function generateRandomBigInt(bytes: number = 8) { + return readBigIntFromBuffer(generateRandomBytes(bytes), true, true); +} + +export function generateRandomInt32() { + return Number(readBigIntFromBuffer(generateRandomBytes(4), true, true)); +} + export async function generateKeyDataFromNonce( - serverNonceBigInt: BigInt.BigInteger, newNonceBigInt: BigInt.BigInteger, + serverNonceBigInt: bigint, newNonceBigInt: bigint, ) { const serverNonce = toSignedLittleBuffer(serverNonceBigInt, 16); const newNonce = toSignedLittleBuffer(newNonceBigInt, 32); @@ -116,36 +151,76 @@ export function sha256(data: Buffer): Promise { } export function modExp( - a: bigInt.BigInteger, - b: bigInt.BigInteger, - n: bigInt.BigInteger, + a: bigint, + b: bigint, + n: bigint, ) { - a = a.remainder(n); - let result = BigInt.one; + a = a % n; + let result = 1n; let x = a; - while (b.greater(BigInt.zero)) { - const leastSignificantBit = b.remainder(BigInt(2)); - b = b.divide(BigInt(2)); - if (leastSignificantBit.eq(BigInt.one)) { - result = result.multiply(x); - result = result.remainder(n); + while (b > 0n) { + const leastSignificantBit = b % 2n; + b = b / 2n; + if (leastSignificantBit === 1n) { + result = result * x; + result = result % n; } - x = x.multiply(x); - x = x.remainder(n); + x = x * x; + x = x % n; } return result; } -export function getByteArray(integer: BigInt.BigInteger, signed = false) { - const bits = integer.toString(2).length; - const byteLength = Math.floor((bits + 8 - 1) / 8); - return readBufferFromBigInt(BigInt(integer), byteLength, false, signed); +export function getByteArray(integer: bigint, signed = false): Buffer { + if (!signed && integer < 0n) { + throw new RangeError('Cannot convert negative to unsigned'); + } + + let bytes: number; + if (signed) { + if (integer >= 0n) { + const bits = bitLength(integer) + 1; + bytes = Math.max(1, Math.ceil(bits / 8)); + } else { + const bits = bitLength(-integer - 1n) + 1; + bytes = Math.max(1, Math.ceil(bits / 8)); + } + } else { + const bits = bitLength(integer); + bytes = Math.max(1, Math.ceil(bits / 8)); + } + + return readBufferFromBigInt(integer, bytes, false, signed); } -export function getRandomInt(min: number, max: number) { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min + 1)) + min; +export function randomBits(k: number): bigint { + if (k <= 0) return 0n; + + const bytes = randomBytes(Math.ceil(k / 8)); + + let r = 0n; + for (let i = 0; i < bytes.length; i++) { + r = (r << 8n) | BigInt(bytes[i]); + } + + return r & ((1n << BigInt(k)) - 1n); +} + +export function randBetweenBigInt(a: bigint, b: bigint): bigint { + const low = a < b ? a : b; + const high = a < b ? b : a; + const range = high - low + 1n; + + if (range <= 1n) return low; + + const k = bitLength(range - 1n); + const twoPowK = 1n << BigInt(k); + const limit = (twoPowK / range) * range; + + for (;;) { + const r = randomBits(k); + if (r < limit) return low + (r % range); + } } export function sleep(ms: number) { @@ -188,3 +263,73 @@ export function crc32(buf: Buffer | string) { } return (crc ^ (-1)) >>> 0; } + +const testersCoeff: number[] = []; +const testersBigCoeff: bigint[] = []; +const testers: bigint[] = []; +let testersN = 0; + +// https://stackoverflow.com/a/76616288 +export function bitLength(x: bigint) { + let k = 0; + while (true) { + if (testersN === k) { + testersCoeff.push(32 << testersN); + testersBigCoeff.push(BigInt(testersCoeff[testersN])); + testers.push(1n << testersBigCoeff[testersN]); + testersN++; + } + if (x < testers[k]) break; + k++; + } + + if (!k) return 32 - Math.clz32(Number(x)); + + // determine length by bisection + k--; + let i = testersCoeff[k]; + let a = x >> testersBigCoeff[k]; + while (k--) { + const b = a >> testersBigCoeff[k]; + if (b) { + i += testersCoeff[k]; + a = b; + } + } + + return i + 32 - Math.clz32(Number(a)); +} + +export const BigMath = { + abs(x: bigint) { + return x < 0n ? -x : x; + }, + sign(x: bigint) { + if (x === 0n) return 0n; + return x < 0n ? -1n : 1n; + }, + pow(base: bigint, exponent: bigint) { + return base ** exponent; + }, + min(value: bigint, ...values: bigint[]) { + for (const v of values) { + if (v < value) value = v; + } + return value; + }, + max(value: bigint, ...values: bigint[]) { + for (const v of values) { + if (v > value) value = v; + } + return value; + }, +}; + +export function jsonStringifyWithBigInt(obj: any) { + return JSON.stringify(obj, (key, value) => { + if (typeof value === 'bigint') { + return value.toString(); + } + return value; + }); +} diff --git a/src/lib/gramjs/Password.ts b/src/lib/gramjs/Password.ts index 23e742986..6651c7723 100644 --- a/src/lib/gramjs/Password.ts +++ b/src/lib/gramjs/Password.ts @@ -1,10 +1,9 @@ -import BigInt from 'big-integer'; - import { pbkdf2 } from './crypto/crypto'; import Api from './tl/api'; import { bigIntMod, + bitLength, generateRandomBytes, modExp, readBigIntFromBuffer, @@ -14,59 +13,6 @@ import { const SIZE_FOR_HASH = 256; -/** - * - * - * @param prime{BigInteger} - * @param g{BigInteger} - */ - -/* -We don't support changing passwords yet -function checkPrimeAndGoodCheck(prime, g) { - console.error('Unsupported function `checkPrimeAndGoodCheck` call. Arguments:', prime, g) - - const goodPrimeBitsCount = 2048 - if (prime < 0 || prime.bitLength() !== goodPrimeBitsCount) { - throw new Error(`bad prime count ${prime.bitLength()},expected ${goodPrimeBitsCount}`) - } - // TODO this is kinda slow - if (Factorizator.factorize(prime)[0] !== 1) { - throw new Error('give "prime" is not prime') - } - if (g.eq(BigInt(2))) { - if ((prime.remainder(BigInt(8))).neq(BigInt(7))) { - throw new Error(`bad g ${g}, mod8 ${prime % 8}`) - } - } else if (g.eq(BigInt(3))) { - if ((prime.remainder(BigInt(3))).neq(BigInt(2))) { - throw new Error(`bad g ${g}, mod3 ${prime % 3}`) - } - // eslint-disable-next-line no-empty - } else if (g.eq(BigInt(4))) { - - } else if (g.eq(BigInt(5))) { - if (!([ BigInt(1), BigInt(4) ].includes(prime.remainder(BigInt(5))))) { - throw new Error(`bad g ${g}, mod8 ${prime % 5}`) - } - } else if (g.eq(BigInt(6))) { - if (!([ BigInt(19), BigInt(23) ].includes(prime.remainder(BigInt(24))))) { - throw new Error(`bad g ${g}, mod8 ${prime % 24}`) - } - } else if (g.eq(BigInt(7))) { - if (!([ BigInt(3), BigInt(5), BigInt(6) ].includes(prime.remainder(BigInt(7))))) { - throw new Error(`bad g ${g}, mod8 ${prime % 7}`) - } - } else { - throw new Error(`bad g ${g}`) - } - const primeSub1Div2 = (prime.subtract(BigInt(1))).divide(BigInt(2)) - if (Factorizator.factorize(primeSub1Div2)[0] !== 1) { - throw new Error('(prime - 1) // 2 is not prime') - } -} -*/ - function checkPrimeAndGood(primeBytes: Buffer, g: number) { const goodPrime = Buffer.from([ 0xC7, 0x1C, 0xAE, 0xB9, 0xC6, 0xB1, 0xC9, 0x04, 0x8E, 0x6C, 0x52, 0x2F, 0x70, 0xF1, 0x3F, 0x73, @@ -95,30 +41,29 @@ function checkPrimeAndGood(primeBytes: Buffer, g: number) { // checkPrimeAndGoodCheck(readBigIntFromBuffer(primeBytes, false), g) } -function isGoodLarge(number: BigInt.BigInteger, p: BigInt.BigInteger): boolean { - return (number.greater(BigInt(0)) && (p.subtract(number) - .greater(BigInt(0)))); +function isGoodLarge(number: bigint, p: bigint): boolean { + return number > 0n && number < p; } function numBytesForHash(number: Buffer): Buffer { return Buffer.concat([Buffer.alloc(SIZE_FOR_HASH - number.length), number]); } -function bigNumForHash(g: BigInt.BigInteger) { +function bigNumForHash(g: bigint) { return readBufferFromBigInt(g, SIZE_FOR_HASH, false); } -function isGoodModExpFirst(modexp: BigInt.BigInteger, prime: BigInt.BigInteger): boolean { - const diff = prime.subtract(modexp); +function isGoodModExpFirst(modexp: bigint, prime: bigint): boolean { + const diff = prime - modexp; const minDiffBitsCount = 2048 - 64; const maxModExpSize = 256; return !( - diff.lesser(BigInt(0)) - || diff.bitLength().toJSNumber() < minDiffBitsCount - || modexp.bitLength().toJSNumber() < minDiffBitsCount - || Math.floor((modexp.bitLength().toJSNumber() + 7) / 8) > maxModExpSize + diff < 0n + || bitLength(diff) < minDiffBitsCount + || bitLength(modexp) < minDiffBitsCount + || Math.floor((bitLength(modexp) + 7) / 8) > maxModExpSize ); } @@ -189,7 +134,7 @@ export async function computeCheck(request: Api.account.Password, password: stri try { checkPrimeAndGood(algo.p, g); } catch (e) { - throw new Error('bad /g in password'); + throw new Error('bad p/g in password'); } if (!isGoodLarge(B, p)) { throw new Error('bad b in check'); @@ -200,7 +145,7 @@ export async function computeCheck(request: Api.account.Password, password: stri const bForHash = numBytesForHash(srpB); const gX = modExp(BigInt(g), x, p); const k = readBigIntFromBuffer(await sha256(Buffer.concat([pForHash, gForHash])), false); - const kgX = bigIntMod(k.multiply(gX), p); + const kgX = bigIntMod(k * gX, p); const generateAndCheckRandom = async () => { const randomSize = 256; @@ -211,20 +156,20 @@ export async function computeCheck(request: Api.account.Password, password: stri if (isGoodModExpFirst(A, p)) { const aForHash = bigNumForHash(A); const u = readBigIntFromBuffer(await sha256(Buffer.concat([aForHash, bForHash])), false); - if (u.greater(BigInt(0))) { + if (u > 0n) { return { a, aForHash, u }; } } } }; const { a, aForHash, u } = await generateAndCheckRandom(); - const gB = bigIntMod(B.subtract(kgX), p); + const gB = bigIntMod(B - kgX, p); if (!isGoodModExpFirst(gB, p)) { throw new Error('bad gB'); } - const ux = u.multiply(x); - const aUx = a.add(ux); + const ux = u * x; + const aUx = a + ux; const S = modExp(gB, aUx, p); const [K, pSha, gSha, salt1Sha, salt2Sha] = await Promise.all([ sha256(bigNumForHash(S)), @@ -244,8 +189,7 @@ export async function computeCheck(request: Api.account.Password, password: stri return new Api.InputCheckPasswordSRP({ srpId, - A: Buffer.from(aForHash), + A: aForHash, M1, - }); } diff --git a/src/lib/gramjs/client/MockClient.ts b/src/lib/gramjs/client/MockClient.ts index e8b223e56..74e80a73e 100644 --- a/src/lib/gramjs/client/MockClient.ts +++ b/src/lib/gramjs/client/MockClient.ts @@ -1,10 +1,9 @@ -import BigInt from 'big-integer'; - import type { DownloadFileWithDcParams } from './downloadFile'; import type { MockTypes } from './mockUtils/MockTypes'; import type { SizeType } from './TelegramClient'; import { GENERAL_TOPIC_ID } from '../../../config'; +import { toJSNumber } from '../../../util/numbers'; import { Logger } from '../extensions'; import { UpdateConnectionState } from '../network'; import Api from '../tl/api'; @@ -195,7 +194,7 @@ class TelegramClient { about: 'lol', settings: new Api.PeerSettings({}), notifySettings: new Api.PeerNotifySettings({}), - id: BigInt(1), + id: 1n, commonChatsCount: 0, }), chats: [], @@ -381,7 +380,7 @@ class TelegramClient { thumbSize: size ? size.type : '', }), { - fileSize: size ? size.size : doc.size.toJSNumber(), + fileSize: size ? size.size : toJSNumber(doc.size), progressCallback: args.progressCallback, start: args.start, end: args.end, diff --git a/src/lib/gramjs/client/TelegramClient.ts b/src/lib/gramjs/client/TelegramClient.ts index 473c3c1b9..71d06c1f6 100644 --- a/src/lib/gramjs/client/TelegramClient.ts +++ b/src/lib/gramjs/client/TelegramClient.ts @@ -1,4 +1,3 @@ -import bigInt from 'big-integer'; import os from 'os'; import type LocalUpdatePremiumFloodWait from '../../../api/gramjs/updates/UpdatePremiumFloodWait'; @@ -16,6 +15,7 @@ import type { DownloadFileParams, DownloadFileWithDcParams, DownloadMediaParams import type { UploadFileParams } from './uploadFile'; import Deferred from '../../../util/Deferred'; +import { toJSNumber } from '../../../util/numbers'; import { FloodTestPhoneWaitError, FloodWaitError, @@ -44,7 +44,7 @@ import { authFlow, checkAuthorization } from './auth'; import { downloadFile } from './downloadFile'; import { uploadFile } from './uploadFile'; -import { getRandomInt, sleep } from '../Helpers'; +import { generateRandomBigInt, sleep } from '../Helpers'; import RequestState from '../network/RequestState'; import Session from '../sessions/Abstract'; import MemorySession from '../sessions/Memory'; @@ -411,7 +411,7 @@ class TelegramClient { return undefined; } return sender.send(new Api.PingDelayDisconnect({ - pingId: bigInt(getRandomInt(Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER)), + pingId: generateRandomBigInt(), disconnectDelay: PING_DISCONNECT_DELAY, })); }; @@ -877,7 +877,7 @@ class TelegramClient { thumbSize: '', }), { - fileSize: doc.size.toJSNumber(), + fileSize: toJSNumber(doc.size), dcId: doc.dcId, }) as Promise; // Sticker thumb cannot be larger than 2GB, right? }); @@ -995,7 +995,7 @@ class TelegramClient { thumbSize: size && 'type' in size ? size.type : '', }), { - fileSize: size && 'size' in size ? size.size : doc.size.toJSNumber(), + fileSize: size && 'size' in size ? size.size : toJSNumber(doc.size), progressCallback: args.progressCallback, start: args.start, end: args.end, @@ -1055,7 +1055,7 @@ class TelegramClient { } async downloadStaticMap( - accessHash: bigInt.BigInteger, + accessHash: bigint, long: number, lat: number, w: number, diff --git a/src/lib/gramjs/client/auth.ts b/src/lib/gramjs/client/auth.ts index 9eea9d17a..29fae702c 100644 --- a/src/lib/gramjs/client/auth.ts +++ b/src/lib/gramjs/client/auth.ts @@ -1,8 +1,7 @@ -import bigInt from 'big-integer'; - import type TelegramClient from './TelegramClient'; import type { Update } from './TelegramClient'; +import { tryParseBigInt } from '../../../util/numbers'; import { getServerTime } from '../../../util/serverTime'; import { DEFAULT_PRIMITIVES } from '../../../api/gramjs/gramjsBuilders'; import { RPCError } from '../errors'; @@ -234,6 +233,7 @@ async function signInUserWithQrCode( let isScanningComplete = false; const { apiId, apiHash } = apiCredentials; + const exceptIds = authParams.accountIds?.map((id) => tryParseBigInt(id)).filter(Boolean) || []; const inputPromise = (async () => { // eslint-disable-next-line no-constant-condition @@ -245,7 +245,7 @@ async function signInUserWithQrCode( const result = await client.invoke(new Api.auth.ExportLoginToken({ apiId, apiHash, - exceptIds: authParams.accountIds?.map((id) => bigInt(id)) || [], + exceptIds, })); if (!(result instanceof Api.auth.LoginToken)) { throw new Error('Unexpected'); @@ -286,7 +286,7 @@ async function signInUserWithQrCode( const result2 = await client.invoke(new Api.auth.ExportLoginToken({ apiId, apiHash, - exceptIds: authParams.accountIds?.map((id) => bigInt(id)) || [], + exceptIds, })); if (result2 instanceof Api.auth.LoginTokenSuccess && result2.authorization instanceof Api.auth.Authorization) { diff --git a/src/lib/gramjs/client/downloadFile.ts b/src/lib/gramjs/client/downloadFile.ts index 1a60d76b1..b3c92fae2 100644 --- a/src/lib/gramjs/client/downloadFile.ts +++ b/src/lib/gramjs/client/downloadFile.ts @@ -1,5 +1,3 @@ -import BigInt from 'big-integer'; - import type TelegramClient from './TelegramClient'; import type { SizeType } from './TelegramClient'; diff --git a/src/lib/gramjs/client/mockUtils/MockTypes.ts b/src/lib/gramjs/client/mockUtils/MockTypes.ts index 782ddad69..1b2c9098a 100644 --- a/src/lib/gramjs/client/mockUtils/MockTypes.ts +++ b/src/lib/gramjs/client/mockUtils/MockTypes.ts @@ -1,5 +1,3 @@ -import type bigInt from 'big-integer'; - import type { GramJsAppConfig } from '../../../../api/gramjs/apiBuilders/appConfig'; import type { ApiAvailableReaction } from '../../../../api/types'; import type Api from '../../tl/api'; @@ -62,7 +60,7 @@ export type MockMessageReactions = { export type MockDocument = Partial & { id: number; mimeType: string; - size: bigInt.BigInteger; + size: bigint; url: string; bytes: Buffer; }; diff --git a/src/lib/gramjs/client/mockUtils/createMockedChannel.ts b/src/lib/gramjs/client/mockUtils/createMockedChannel.ts index 6dfff90df..d8ec1e8f2 100644 --- a/src/lib/gramjs/client/mockUtils/createMockedChannel.ts +++ b/src/lib/gramjs/client/mockUtils/createMockedChannel.ts @@ -1,7 +1,6 @@ -import BigInt from 'big-integer'; - import type { MockTypes } from './MockTypes'; +import { CHANNEL_ID_BASE } from '../../../../config'; import Api from '../../tl/api'; import createMockedChatAdminRights from './createMockedChatAdminRights'; import createMockedChatBannedRights from './createMockedChatBannedRights'; @@ -14,7 +13,7 @@ export default function createMockedChannel(id: string, mockData: MockTypes): Ap if (!channel) throw Error('No such channel ' + id); const { - accessHash = BigInt(1), + accessHash = 1n, title = 'Channel', date = MOCK_STARTING_DATE, bannedRights = createMockedChatBannedRights(id, mockData), @@ -24,7 +23,7 @@ export default function createMockedChannel(id: string, mockData: MockTypes): Ap return new Api.Channel({ ...rest, - id: BigInt(Number(id) + 1000000000), + id: -BigInt(id) - CHANNEL_ID_BASE, accessHash, title, bannedRights, diff --git a/src/lib/gramjs/client/mockUtils/createMockedChat.ts b/src/lib/gramjs/client/mockUtils/createMockedChat.ts index c9fa89e6a..e74ca2459 100644 --- a/src/lib/gramjs/client/mockUtils/createMockedChat.ts +++ b/src/lib/gramjs/client/mockUtils/createMockedChat.ts @@ -1,5 +1,3 @@ -import BigInt from 'big-integer'; - import type { MockTypes } from './MockTypes'; import Api from '../../tl/api'; diff --git a/src/lib/gramjs/client/mockUtils/createMockedDocument.ts b/src/lib/gramjs/client/mockUtils/createMockedDocument.ts index a7d27833d..e546573e9 100644 --- a/src/lib/gramjs/client/mockUtils/createMockedDocument.ts +++ b/src/lib/gramjs/client/mockUtils/createMockedDocument.ts @@ -1,5 +1,3 @@ -import BigInt from 'big-integer'; - import type { MockTypes } from './MockTypes'; import Api from '../../tl/api'; @@ -12,7 +10,7 @@ export default function createMockedDocument(documentId: number, mockData: MockT if (!document) throw Error('No such document ' + documentId); const { - accessHash = BigInt(1), + accessHash = 1n, fileReference = Buffer.from([0]), date = MOCK_STARTING_DATE, dcId = 2, diff --git a/src/lib/gramjs/client/mockUtils/createMockedPhoto.ts b/src/lib/gramjs/client/mockUtils/createMockedPhoto.ts index 82ac9410e..b7fb06a49 100644 --- a/src/lib/gramjs/client/mockUtils/createMockedPhoto.ts +++ b/src/lib/gramjs/client/mockUtils/createMockedPhoto.ts @@ -1,7 +1,6 @@ -import BigInt from 'big-integer'; - import type { MockTypes } from './MockTypes'; +import { toJSNumber } from '../../../../util/numbers'; import Api from '../../tl/api'; import { MOCK_STARTING_DATE } from './MockTypes'; @@ -12,7 +11,7 @@ export default function createMockedPhoto(documentId: number, mockData: MockType if (!document) throw Error('No such document ' + documentId); const { - accessHash = BigInt(1), + accessHash = 1n, fileReference = Buffer.from([0]), date = MOCK_STARTING_DATE, dcId = 2, @@ -32,13 +31,13 @@ export default function createMockedPhoto(documentId: number, mockData: MockType type: 'm', w: 100, h: 100, - size: size.toJSNumber(), + size: toJSNumber(size), }), new Api.PhotoSize({ type: 'x', w: 100, h: 100, - size: size.toJSNumber(), + size: toJSNumber(size), }), ], // thumbs?: Api.TypePhotoSize[]; diff --git a/src/lib/gramjs/client/mockUtils/createMockedReplies.ts b/src/lib/gramjs/client/mockUtils/createMockedReplies.ts index b47669669..7419a6b18 100644 --- a/src/lib/gramjs/client/mockUtils/createMockedReplies.ts +++ b/src/lib/gramjs/client/mockUtils/createMockedReplies.ts @@ -1,7 +1,6 @@ -import BigInt from 'big-integer'; - import type { MockTypes } from './MockTypes'; +import { CHANNEL_ID_BASE } from '../../../../config'; import Api from '../../tl/api'; export default function createMockedReplies(chatId: string, id: number, mockData: MockTypes) { @@ -19,7 +18,7 @@ export default function createMockedReplies(chatId: string, id: number, mockData comments: true, replies: replies.replies, repliesPts: 1, - channelId: BigInt(1000000000 + 2), + channelId: -2n - CHANNEL_ID_BASE, // recentRepliers?: Api.TypePeer[]; }); } diff --git a/src/lib/gramjs/client/mockUtils/createMockedTypeInputPeer.ts b/src/lib/gramjs/client/mockUtils/createMockedTypeInputPeer.ts index a0fc888e8..b3b427a16 100644 --- a/src/lib/gramjs/client/mockUtils/createMockedTypeInputPeer.ts +++ b/src/lib/gramjs/client/mockUtils/createMockedTypeInputPeer.ts @@ -1,7 +1,6 @@ -import BigInt from 'big-integer'; - import type { MockTypes } from './MockTypes'; +import { CHANNEL_ID_BASE } from '../../../../config'; import Api from '../../tl/api'; export default function createMockedTypeInputPeer(id: string, mockData: MockTypes): Api.TypeInputPeer { @@ -9,7 +8,7 @@ export default function createMockedTypeInputPeer(id: string, mockData: MockType if (user) { return new Api.InputPeerUser({ userId: BigInt(id), - accessHash: BigInt(1), + accessHash: 1n, }); } @@ -23,8 +22,8 @@ export default function createMockedTypeInputPeer(id: string, mockData: MockType const channel = mockData.channels.find((c) => c.id === id); if (channel) { return new Api.InputPeerChannel({ - channelId: BigInt(Number(id) + 1000000000), - accessHash: BigInt(1), + channelId: -BigInt(id) - CHANNEL_ID_BASE, + accessHash: 1n, }); } diff --git a/src/lib/gramjs/client/mockUtils/createMockedTypePeer.ts b/src/lib/gramjs/client/mockUtils/createMockedTypePeer.ts index 7df5a2465..4bc2429a2 100644 --- a/src/lib/gramjs/client/mockUtils/createMockedTypePeer.ts +++ b/src/lib/gramjs/client/mockUtils/createMockedTypePeer.ts @@ -1,7 +1,6 @@ -import BigInt from 'big-integer'; - import type { MockTypes } from './MockTypes'; +import { CHANNEL_ID_BASE } from '../../../../config'; import Api from '../../tl/api'; export default function createMockedTypePeer(id: string, mockData: MockTypes): Api.TypePeer { @@ -22,7 +21,7 @@ export default function createMockedTypePeer(id: string, mockData: MockTypes): A const channel = mockData.channels.find((c) => c.id === id); if (channel) { return new Api.PeerChannel({ - channelId: BigInt(Number(id) + 1000000000), + channelId: -BigInt(id) - CHANNEL_ID_BASE, }); } diff --git a/src/lib/gramjs/client/mockUtils/createMockedUser.ts b/src/lib/gramjs/client/mockUtils/createMockedUser.ts index 6009ed6ce..47005fb2e 100644 --- a/src/lib/gramjs/client/mockUtils/createMockedUser.ts +++ b/src/lib/gramjs/client/mockUtils/createMockedUser.ts @@ -1,5 +1,3 @@ -import BigInt from 'big-integer'; - import type { MockTypes } from './MockTypes'; import Api from '../../tl/api'; @@ -12,7 +10,7 @@ export default function createMockedUser(id: string, mockData: MockTypes): Api.U const { firstName = 'John', lastName = 'Doe', - accessHash = BigInt(1), + accessHash = 1n, ...rest } = user; diff --git a/src/lib/gramjs/client/mockUtils/getDocumentIdFromLocation.ts b/src/lib/gramjs/client/mockUtils/getDocumentIdFromLocation.ts index fe72f3822..4c9974bc3 100644 --- a/src/lib/gramjs/client/mockUtils/getDocumentIdFromLocation.ts +++ b/src/lib/gramjs/client/mockUtils/getDocumentIdFromLocation.ts @@ -1,13 +1,14 @@ +import { toJSNumber } from '../../../../util/numbers'; import Api from '../../tl/api'; export default function getDocumentIdFromLocation(location: Api.TypeInputFileLocation): number { if (location instanceof Api.InputDocumentFileLocation) { - return location.id.toJSNumber(); + return toJSNumber(location.id); } if (location instanceof Api.InputPhotoFileLocation) { - return location.id.toJSNumber(); + return toJSNumber(location.id); } - throw Error('Unsupported input file location type ' + location.className); + throw new Error(`Unsupported input file location type ${location.className}`); } diff --git a/src/lib/gramjs/client/mockUtils/getIdFromInputPeer.ts b/src/lib/gramjs/client/mockUtils/getIdFromInputPeer.ts index 3521ca77d..c2fb94855 100644 --- a/src/lib/gramjs/client/mockUtils/getIdFromInputPeer.ts +++ b/src/lib/gramjs/client/mockUtils/getIdFromInputPeer.ts @@ -1,8 +1,9 @@ +import { CHANNEL_ID_BASE } from '../../../../config'; import Api from '../../tl/api'; export default function getIdFromInputPeer(peer: Api.TypeInputPeer | Api.TypeInputChannel) { if (peer instanceof Api.InputPeerChannel || peer instanceof Api.InputChannel) { - return (Number(peer.channelId.toString()) - 1000000000).toString(); + return (-peer.channelId - CHANNEL_ID_BASE).toString(); } if (peer instanceof Api.InputPeerUser) { @@ -10,7 +11,7 @@ export default function getIdFromInputPeer(peer: Api.TypeInputPeer | Api.TypeInp } if (peer instanceof Api.InputPeerChat) { - return peer.chatId.toString(); + return (-peer.chatId).toString(); } throw Error(`Unknown peer type${peer.className}`); diff --git a/src/lib/gramjs/crypto/AuthKey.ts b/src/lib/gramjs/crypto/AuthKey.ts index c6f9d2995..8f8cf38e8 100644 --- a/src/lib/gramjs/crypto/AuthKey.ts +++ b/src/lib/gramjs/crypto/AuthKey.ts @@ -1,5 +1,3 @@ -import type BigInt from 'big-integer'; - import { BinaryReader } from '../extensions'; import { @@ -15,9 +13,9 @@ export class AuthKey { _hash?: Buffer; - private auxHash?: BigInt.BigInteger; + private auxHash?: bigint; - keyId?: BigInt.BigInteger; + keyId?: bigint; constructor(value?: Buffer, hash?: Buffer) { if (!hash || !value) { @@ -55,7 +53,7 @@ export class AuthKey { } async waitForKey() { - while (!this.keyId) { + while (this.keyId === undefined) { await sleep(20); } } @@ -70,13 +68,13 @@ export class AuthKey { * Calculates the new nonce hash based on the current class fields' values * @param newNonce * @param number - * @returns {BigInt.BigInteger} + * @returns {bigint} */ async calcNewNonceHash( - newNonce: BigInt.BigInteger, + newNonce: bigint, number: number, - ): Promise { - if (!this.auxHash) { + ): Promise { + if (this.auxHash === undefined) { throw new Error('Auth key not set'); } diff --git a/src/lib/gramjs/crypto/Factorizator.ts b/src/lib/gramjs/crypto/Factorizator.ts index bf9e1d436..0f9026882 100644 --- a/src/lib/gramjs/crypto/Factorizator.ts +++ b/src/lib/gramjs/crypto/Factorizator.ts @@ -1,18 +1,16 @@ -import BigInt from 'big-integer'; - -import { modExp } from '../Helpers'; +import { BigMath, modExp, randBetweenBigInt } from '../Helpers'; export class Factorizator { /** * Calculates the greatest common divisor - * @param a {BigInteger} - * @param b {BigInteger} - * @returns {BigInteger} + * @param a {bigint} + * @param b {bigint} + * @returns {bigint} */ - static gcd(a: BigInt.BigInteger, b: BigInt.BigInteger) { - while (b.neq(BigInt.zero)) { + static gcd(a: bigint, b: bigint) { + while (b !== 0n) { const temp = b; - b = a.remainder(b); + b = a % b; a = temp; } return a; @@ -20,57 +18,57 @@ export class Factorizator { /** * Factorizes the given number and returns both the divisor and the number divided by the divisor - * @param pq {BigInteger} + * @param pq {bigint} * @returns {{p: *, q: *}} */ - static factorize(pq: BigInt.BigInteger) { - if (pq.remainder(2).equals(BigInt.zero)) { - return { p: BigInt(2), q: pq.divide(BigInt(2)) }; + static factorize(pq: bigint) { + if (pq % 2n === 0n) { + return { p: 2n, q: pq / 2n }; } - let y = BigInt.randBetween(BigInt(1), pq.minus(1)); - const c = BigInt.randBetween(BigInt(1), pq.minus(1)); - const m = BigInt.randBetween(BigInt(1), pq.minus(1)); + let y = randBetweenBigInt(1n, pq - 1n); + const c = randBetweenBigInt(1n, pq - 1n); + const m = randBetweenBigInt(1n, pq - 1n); - let g = BigInt.one; - let r = BigInt.one; - let q = BigInt.one; - let x = BigInt.zero; - let ys = BigInt.zero; - let k; + let g = 1n; + let r = 1n; + let q = 1n; + let x = 0n; + let ys = 0n; + let k: bigint; - while (g.eq(BigInt.one)) { + while (g === 1n) { x = y; - for (let i = 0; BigInt(i).lesser(r); i++) { - y = modExp(y, BigInt(2), pq).add(c).remainder(pq); + for (let i = 0n; i < r; i++) { + y = (modExp(y, 2n, pq) + c) % pq; } - k = BigInt.zero; + k = 0n; - while (k.lesser(r) && g.eq(BigInt.one)) { + while (k < r && g === 1n) { ys = y; - const condition = BigInt.min(m, r.minus(k)); - for (let i = 0; BigInt(i).lesser(condition); i++) { - y = modExp(y, BigInt(2), pq).add(c).remainder(pq); - q = q.multiply(x.minus(y).abs()).remainder(pq); + const condition = BigMath.min(m, r - k); + for (let i = 0n; i < condition; i++) { + y = (modExp(y, 2n, pq) + c) % pq; + q = (q * BigMath.abs(x - y)) % pq; } g = Factorizator.gcd(q, pq); - k = k.add(m); + k = k + m; } - r = r.multiply(2); + r = r * 2n; } - if (g.eq(pq)) { + if (g === pq) { while (true) { - ys = modExp(ys, BigInt(2), pq).add(c).remainder(pq); - g = Factorizator.gcd(x.minus(ys).abs(), pq); + ys = (modExp(ys, 2n, pq) + c) % pq; + g = Factorizator.gcd(BigMath.abs(x - ys), pq); - if (g.greater(1)) { + if (g > 1n) { break; } } } const p = g; - q = pq.divide(g); + q = pq / g; return p < q ? { p, q } : { p: q, q: p }; } } diff --git a/src/lib/gramjs/crypto/RSA.ts b/src/lib/gramjs/crypto/RSA.ts index 8c2f3cccd..524f1330f 100644 --- a/src/lib/gramjs/crypto/RSA.ts +++ b/src/lib/gramjs/crypto/RSA.ts @@ -1,5 +1,3 @@ -import bigInt from 'big-integer'; - import { generateRandomBytes, modExp, @@ -10,8 +8,8 @@ import { export const SERVER_KEYS = [ { - fingerprint: bigInt('-3414540481677951611'), - n: bigInt( + fingerprint: BigInt('-3414540481677951611'), + n: BigInt( '2937959817066933702298617714945612856538843112005886376816255642404751219133084745514657634448776440866' + '1701890505066208632169112269581063774293102577308490531282748465986139880977280302242772832972539403531' + '3160108704012876427630091361567343395380424193887227773571344877461690935390938502512438971889287359033' @@ -22,8 +20,8 @@ export const SERVER_KEYS = [ e: 65537, }, { - fingerprint: bigInt('-5595554452916591101'), - n: bigInt( + fingerprint: BigInt('-5595554452916591101'), + n: BigInt( '2534288944884041556497168959071347320689884775908477905258202659454602246385394058588521595116849196570' + '8222649399180603818074200620463776135424884632162512403163793083921641631564740959529419359595852941166' + '8489405859523376133330223960965841179548922160312292373029437018775884567383353986024616752250817918203' @@ -34,9 +32,9 @@ export const SERVER_KEYS = [ e: 65537, }, ].reduce((acc, { fingerprint, ...keyInfo }) => { - acc.set(fingerprint.toString(), keyInfo); + acc.set(fingerprint, keyInfo); return acc; -}, new Map()); +}, new Map()); /** * Encrypts the given data known the fingerprint to be used @@ -46,8 +44,8 @@ export const SERVER_KEYS = [ * @param data the data to be encrypted. * @returns {Buffer|*|undefined} the cipher text, or undefined if no key matching this fingerprint is found. */ -export async function encrypt(fingerprint: bigInt.BigInteger, data: Buffer) { - const key = SERVER_KEYS.get(fingerprint.toString()); +export async function encrypt(fingerprint: bigint, data: Buffer) { + const key = SERVER_KEYS.get(fingerprint); if (!key) { return undefined; } @@ -60,7 +58,7 @@ export async function encrypt(fingerprint: bigInt.BigInteger, data: Buffer) { // rsa module rsa.encrypt adds 11 bits for padding which we don't want // rsa module uses rsa.transform.bytes2int(to_encrypt), easier way: const payload = readBigIntFromBuffer(toEncrypt, false); - const encrypted = modExp(payload, bigInt(key.e), key.n); + const encrypted = modExp(payload, BigInt(key.e), key.n); // rsa module uses transform.int2bytes(encrypted, keylength), easier: return readBufferFromBigInt(encrypted, 256, false); } diff --git a/src/lib/gramjs/extensions/BinaryReader.ts b/src/lib/gramjs/extensions/BinaryReader.ts index 37bff81fb..fce24d506 100644 --- a/src/lib/gramjs/extensions/BinaryReader.ts +++ b/src/lib/gramjs/extensions/BinaryReader.ts @@ -50,7 +50,7 @@ export default class BinaryReader { /** * Reads a long integer (8 bytes or 64 bits) value. * @param signed - * @returns {BigInteger} + * @returns {bigint} */ readLong(signed = true) { return this.readLargeInt(64, signed); @@ -66,7 +66,7 @@ export default class BinaryReader { /** * Reads a real floating point (8 bytes) value. - * @returns {BigInteger} + * @returns {number} */ readDouble() { // was this a bug ? it should have been ; + _pending: Map; constructor() { this._pending = new Map(); } - set(msgId: BigInt.BigInteger, state: RequestState) { - this._pending.set(msgId.toString(), state); + set(msgId: bigint, state: RequestState) { + this._pending.set(msgId, state); } - get(msgId: BigInt.BigInteger) { - return this._pending.get(msgId.toString()); + get(msgId: bigint) { + return this._pending.get(msgId); } - getAndDelete(msgId: BigInt.BigInteger) { + getAndDelete(msgId: bigint) { const state = this.get(msgId); this.delete(msgId); return state; @@ -27,8 +25,8 @@ export default class PendingState { return Array.from(this._pending.values()); } - delete(msgId: BigInt.BigInteger) { - this._pending.delete(msgId.toString()); + delete(msgId: bigint) { + return this._pending.delete(msgId); } clear() { diff --git a/src/lib/gramjs/helpers.test.ts b/src/lib/gramjs/helpers.test.ts new file mode 100644 index 000000000..e3e8a3c3b --- /dev/null +++ b/src/lib/gramjs/helpers.test.ts @@ -0,0 +1,247 @@ +import { + readBigIntFromBuffer, + readBufferFromBigInt, + toSignedLittleBuffer, +} from './Helpers'; + +describe('readBigIntFromBuffer', () => { + describe('little endian unsigned', () => { + it('should read 8-byte buffer using native method', () => { + const buffer = Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); + const result = readBigIntFromBuffer(buffer, true, false); + expect(result).toBe(0x0807060504030201n); + }); + + it('should read 4-byte buffer', () => { + const buffer = Buffer.from([0xFF, 0xFF, 0xFF, 0xFF]); + const result = readBigIntFromBuffer(buffer, true, false); + expect(result).toBe(0xFFFFFFFFn); + }); + + it('should read 2-byte buffer', () => { + const buffer = Buffer.from([0x34, 0x12]); + const result = readBigIntFromBuffer(buffer, true, false); + expect(result).toBe(0x1234n); + }); + }); + + describe('big endian unsigned', () => { + it('should read 8-byte buffer using native method', () => { + const buffer = Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); + const result = readBigIntFromBuffer(buffer, false, false); + expect(result).toBe(0x0102030405060708n); + }); + + it('should read 4-byte buffer', () => { + const buffer = Buffer.from([0x12, 0x34, 0x56, 0x78]); + const result = readBigIntFromBuffer(buffer, false, false); + expect(result).toBe(0x12345678n); + }); + }); + + describe('signed values', () => { + it('should read positive signed 8-byte value', () => { + const buffer = Buffer.from([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + const result = readBigIntFromBuffer(buffer, true, true); + expect(result).toBe(1n); + }); + + it('should read negative signed 8-byte value', () => { + const buffer = Buffer.from([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]); + const result = readBigIntFromBuffer(buffer, true, true); + expect(result).toBe(-1n); + }); + + it('should read negative signed 4-byte value', () => { + const buffer = Buffer.from([0xFF, 0xFF, 0xFF, 0xFF]); + const result = readBigIntFromBuffer(buffer, true, true); + expect(result).toBe(-1n); + }); + + it('should read max signed 8-byte value', () => { + const buffer = Buffer.from([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F]); + const result = readBigIntFromBuffer(buffer, true, true); + expect(result).toBe(0x7FFFFFFFFFFFFFFFn); + }); + + it('should read min signed 8-byte value', () => { + const buffer = Buffer.from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80]); + const result = readBigIntFromBuffer(buffer, true, true); + expect(result).toBe(-0x8000000000000000n); + }); + }); +}); + +describe('toSignedLittleBuffer', () => { + describe('8-byte buffers (native method)', () => { + it('should convert positive value', () => { + const buffer = toSignedLittleBuffer(0x0102030405060708n, 8); + expect(buffer).toEqual(Buffer.from([0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01])); + }); + + it('should convert negative value', () => { + const buffer = toSignedLittleBuffer(-1n, 8); + expect(buffer).toEqual(Buffer.from([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])); + }); + + it('should convert zero', () => { + const buffer = toSignedLittleBuffer(0n, 8); + expect(buffer).toEqual(Buffer.from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])); + }); + + it('should convert max signed 64-bit value', () => { + const buffer = toSignedLittleBuffer(0x7FFFFFFFFFFFFFFFn, 8); + expect(buffer).toEqual(Buffer.from([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F])); + }); + + it('should convert min signed 64-bit value', () => { + const buffer = toSignedLittleBuffer(-0x8000000000000000n, 8); + expect(buffer).toEqual(Buffer.from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80])); + }); + }); + + describe('non-8-byte buffers', () => { + it('should convert 4-byte value', () => { + const buffer = toSignedLittleBuffer(0x12345678n, 4); + expect(buffer).toEqual(Buffer.from([0x78, 0x56, 0x34, 0x12])); + }); + + it('should convert 2-byte value', () => { + const buffer = toSignedLittleBuffer(0x1234n, 2); + expect(buffer).toEqual(Buffer.from([0x34, 0x12])); + }); + + it('should convert 16-byte value', () => { + const buffer = toSignedLittleBuffer(0x0102030405060708090A0B0C0D0E0F10n, 16); + expect(buffer).toEqual(Buffer.from([ + 0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, + 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, + ])); + }); + + it('should handle negative values for non-8-byte buffers', () => { + const buffer = toSignedLittleBuffer(-1n, 4); + expect(buffer).toEqual(Buffer.from([0xFF, 0xFF, 0xFF, 0xFF])); + }); + }); +}); + +describe('readBufferFromBigInt', () => { + describe('8-byte buffers (native methods)', () => { + it('should write unsigned little endian', () => { + const buffer = readBufferFromBigInt(0x0102030405060708n, 8, true, false); + expect(buffer).toEqual(Buffer.from([0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01])); + }); + + it('should write unsigned big endian', () => { + const buffer = readBufferFromBigInt(0x0102030405060708n, 8, false, false); + expect(buffer).toEqual(Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08])); + }); + + it('should write signed positive little endian', () => { + const buffer = readBufferFromBigInt(1234567890n, 8, true, true); + const read = readBigIntFromBuffer(buffer, true, true); + expect(read).toBe(1234567890n); + }); + + it('should write signed negative little endian', () => { + const buffer = readBufferFromBigInt(-1n, 8, true, true); + expect(buffer).toEqual(Buffer.from([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])); + }); + + it('should write signed negative big endian', () => { + const buffer = readBufferFromBigInt(-1n, 8, false, true); + expect(buffer).toEqual(Buffer.from([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])); + }); + }); + + describe('non-8-byte buffers', () => { + it('should write 4-byte unsigned little endian', () => { + const buffer = readBufferFromBigInt(0x12345678n, 4, true, false); + expect(buffer).toEqual(Buffer.from([0x78, 0x56, 0x34, 0x12])); + }); + + it('should write 4-byte unsigned big endian', () => { + const buffer = readBufferFromBigInt(0x12345678n, 4, false, false); + expect(buffer).toEqual(Buffer.from([0x12, 0x34, 0x56, 0x78])); + }); + + it('should write 2-byte value', () => { + const buffer = readBufferFromBigInt(0x1234n, 2, true, false); + expect(buffer).toEqual(Buffer.from([0x34, 0x12])); + }); + + it('should write 16-byte value', () => { + const buffer = readBufferFromBigInt(0x0102030405060708090A0B0C0D0E0F10n, 16, false, false); + expect(buffer).toEqual(Buffer.from([ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, + ])); + }); + + it('should write signed negative 4-byte value', () => { + const buffer = readBufferFromBigInt(-1n, 4, true, true); + expect(buffer).toEqual(Buffer.from([0xFF, 0xFF, 0xFF, 0xFF])); + }); + + it('should write signed negative 4-byte value big endian', () => { + const buffer = readBufferFromBigInt(-1n, 4, false, true); + expect(buffer).toEqual(Buffer.from([0xFF, 0xFF, 0xFF, 0xFF])); + }); + + it('should pad with zeros for smaller values', () => { + const buffer = readBufferFromBigInt(0xFFn, 4, true, false); + expect(buffer).toEqual(Buffer.from([0xFF, 0x00, 0x00, 0x00])); + }); + }); + + describe('error handling', () => { + it('should throw when converting negative to unsigned', () => { + expect(() => readBufferFromBigInt(-1n, 8, true, false)) + .toThrow('Cannot convert negative to unsigned'); + }); + + it('should throw when value is too large for buffer', () => { + const largeValue = 0x1FFFFFFFFFFFFFFFFn; // Too large for 8 bytes + expect(() => readBufferFromBigInt(largeValue, 8, true, false)) + .toThrow('Value 36893488147419103231 does not fit in 8 unsigned bytes'); + }); + }); + + describe('round-trip consistency', () => { + it('should maintain value through read/write cycle (unsigned)', () => { + const original = 0x123456789ABCDEFn; + const buffer = readBufferFromBigInt(original, 8, true, false); + const result = readBigIntFromBuffer(buffer, true, false); + expect(result).toBe(original); + }); + + it('should maintain value through read/write cycle (signed positive)', () => { + const original = 0x123456789ABCDEFn; + const buffer = readBufferFromBigInt(original, 8, true, true); + const result = readBigIntFromBuffer(buffer, true, true); + expect(result).toBe(original); + }); + + it('should maintain value through read/write cycle (signed negative)', () => { + const original = -0x123456789ABCDEFn; + const buffer = readBufferFromBigInt(original, 8, true, true); + const result = readBigIntFromBuffer(buffer, true, true); + expect(result).toBe(original); + }); + + it('should maintain value through read/write cycle (big endian)', () => { + const original = 0x123456789ABCDEFn; + const buffer = readBufferFromBigInt(original, 8, false, false); + const result = readBigIntFromBuffer(buffer, false, false); + expect(result).toBe(original); + }); + + it('should maintain value for 4-byte buffers', () => { + const original = 0x12345678n; + const buffer = readBufferFromBigInt(original, 4, true, false); + const result = readBigIntFromBuffer(buffer, true, false); + expect(result).toBe(original); + }); + }); +}); diff --git a/src/lib/gramjs/network/Authenticator.ts b/src/lib/gramjs/network/Authenticator.ts index 6d0fedeae..ae3ee455d 100644 --- a/src/lib/gramjs/network/Authenticator.ts +++ b/src/lib/gramjs/network/Authenticator.ts @@ -5,8 +5,6 @@ * @returns {Promise<{authKey: *, timeOffset: *}>} */ -import bigInt from 'big-integer'; - import type MTProtoPlainSender from './MTProtoPlainSender'; import { IGE } from '../crypto/IGE'; @@ -43,7 +41,7 @@ export async function doAuthentication(sender: MTProtoPlainSender, log: any) { if (!(resPQ instanceof Api.ResPQ)) { throw new SecurityError(`Step 1 answer was ${resPQ}`); } - if (resPQ.nonce.neq(nonce)) { + if (resPQ.nonce !== nonce) { throw new SecurityError('Step 1 invalid nonce from server'); } const pq = readBigIntFromBuffer(resPQ.pq, false, true); @@ -70,7 +68,7 @@ export async function doAuthentication(sender: MTProtoPlainSender, log: any) { let targetFingerprint; let targetKey; for (const fingerprint of resPQ.serverPublicKeyFingerprints) { - targetKey = SERVER_KEYS.get(fingerprint.toString()); + targetKey = SERVER_KEYS.get(fingerprint); if (targetKey !== undefined) { targetFingerprint = fingerprint; break; @@ -98,11 +96,11 @@ export async function doAuthentication(sender: MTProtoPlainSender, log: any) { const keyAesEncrypted = Buffer.concat([tempKeyXor, aesEncrypted]); const keyAesEncryptedInt = readBigIntFromBuffer(keyAesEncrypted, false, false); - if (keyAesEncryptedInt.greaterOrEquals(targetKey.n)) { + if (keyAesEncryptedInt >= targetKey.n) { log.debug('Aes key greater than RSA. retrying'); continue; } - const encryptedDataBuffer = modExp(keyAesEncryptedInt, bigInt(targetKey.e), targetKey.n); + const encryptedDataBuffer = modExp(keyAesEncryptedInt, BigInt(targetKey.e), targetKey.n); encryptedData = readBufferFromBigInt(encryptedDataBuffer, 256, false, false); break; @@ -133,11 +131,11 @@ export async function doAuthentication(sender: MTProtoPlainSender, log: any) { ) { throw new Error(`Step 2.1 answer was ${serverDhParams}`); } - if (serverDhParams.nonce.neq(resPQ.nonce)) { + if (serverDhParams.nonce !== resPQ.nonce) { throw new SecurityError('Step 2 invalid nonce from server'); } - if (serverDhParams.serverNonce.neq(resPQ.serverNonce)) { + if (serverDhParams.serverNonce !== resPQ.serverNonce) { throw new SecurityError('Step 2 invalid server nonce from server'); } @@ -146,7 +144,7 @@ export async function doAuthentication(sender: MTProtoPlainSender, log: any) { toSignedLittleBuffer(newNonce, 32).slice(4, 20), ); const nnh = readBigIntFromBuffer(sh, true, true); - if (serverDhParams.newNonceHash.neq(nnh)) { + if (serverDhParams.newNonceHash !== nnh) { throw new SecurityError('Step 2 invalid DH fail nonce from server'); } } @@ -178,10 +176,10 @@ export async function doAuthentication(sender: MTProtoPlainSender, log: any) { throw new SecurityError('Step 3 Invalid hash answer'); } - if (serverDhInner.nonce.neq(resPQ.nonce)) { + if (serverDhInner.nonce !== resPQ.nonce) { throw new SecurityError('Step 3 Invalid nonce in encrypted answer'); } - if (serverDhInner.serverNonce.neq(resPQ.serverNonce)) { + if (serverDhInner.serverNonce !== resPQ.serverNonce) { throw new SecurityError( 'Step 3 Invalid server nonce in encrypted answer', ); @@ -207,26 +205,26 @@ export async function doAuthentication(sender: MTProtoPlainSender, log: any) { false, false, ); - const gb = modExp(bigInt(serverDhInner.g), b, dhPrime); + const gb = modExp(BigInt(serverDhInner.g), b, dhPrime); const gab = modExp(ga, b, dhPrime); - if (ga.lesserOrEquals(1)) { + if (ga <= 1n) { throw new SecurityError('Step 3 failed ga > 1 check'); } - if (gb.lesserOrEquals(1)) { + if (gb <= 1n) { throw new SecurityError('Step 3 failed gb > 1 check'); } - if (ga.greater(dhPrime.minus(1))) { - throw new SecurityError('Step 3 failed ga > dh_prime - 1 check'); + if (ga >= (dhPrime - 1n)) { + throw new SecurityError('Step 3 failed ga < dh_prime - 1 check'); } - const toCheckAgainst = bigInt(2).pow(2048 - 64); - if (!(ga.greaterOrEquals(toCheckAgainst) && ga.lesserOrEquals(dhPrime.minus(toCheckAgainst)))) { + const toCheckAgainst = 2n ** (2048n - 64n); + if (!(ga > toCheckAgainst && ga < (dhPrime - toCheckAgainst))) { throw new SecurityError('Step 3 failed dh_prime - 2^{2048-64} < ga < 2^{2048-64} check'); } - if (!(gb.greaterOrEquals(toCheckAgainst) && gb.lesserOrEquals(dhPrime.minus(toCheckAgainst)))) { + if (!(gb > toCheckAgainst && gb < (dhPrime - toCheckAgainst))) { throw new SecurityError('Step 3 failed dh_prime - 2^{2048-64} < gb < 2^{2048-64} check'); } @@ -234,7 +232,7 @@ export async function doAuthentication(sender: MTProtoPlainSender, log: any) { const clientDhInner = new Api.ClientDHInnerData({ nonce: resPQ.nonce, serverNonce: resPQ.serverNonce, - retryId: bigInt.zero, // TODO Actual retry ID + retryId: 0n, // TODO Actual retry ID gB: getByteArray(gb, false), }).getBytes(); @@ -265,10 +263,10 @@ export async function doAuthentication(sender: MTProtoPlainSender, log: any) { throw new Error(`Step 3.1 answer was ${dhGen}`); } const { name } = dhGen.constructor; - if (dhGen.nonce.neq(resPQ.nonce)) { + if (dhGen.nonce !== resPQ.nonce) { throw new SecurityError(`Step 3 invalid ${name} nonce from server`); } - if (dhGen.serverNonce.neq(resPQ.serverNonce)) { + if (dhGen.serverNonce !== resPQ.serverNonce) { throw new SecurityError( `Step 3 invalid ${name} server nonce from server`, ); @@ -279,10 +277,10 @@ export async function doAuthentication(sender: MTProtoPlainSender, log: any) { const nonceNumber = 1 + nonceTypesString.indexOf(dhGen.className); const newNonceHash = await authKey.calcNewNonceHash(newNonce, nonceNumber); - // @ts-ignore - const dhHash = dhGen[`newNonceHash${nonceNumber}`]; + // @ts-expect-error + const dhHash = dhGen[`newNonceHash${nonceNumber}`] as bigint; - if (dhHash.neq(newNonceHash)) { + if (dhHash !== newNonceHash) { throw new SecurityError('Step 3 invalid new nonce hash'); } diff --git a/src/lib/gramjs/network/MTProtoPlainSender.ts b/src/lib/gramjs/network/MTProtoPlainSender.ts index 92b559302..7a98956cc 100644 --- a/src/lib/gramjs/network/MTProtoPlainSender.ts +++ b/src/lib/gramjs/network/MTProtoPlainSender.ts @@ -2,7 +2,6 @@ * This module contains the class used to communicate with Telegram's servers * in plain text, when no authorization key has been created yet. */ -import BigInt from 'big-integer'; import type { Logger } from '../extensions'; import type { Api } from '../tl'; @@ -53,11 +52,11 @@ export default class MTProtoPlainSender { } const reader = new BinaryReader(body); const authKeyId = reader.readLong(); - if (authKeyId.neq(BigInt(0))) { + if (authKeyId !== 0n) { throw new Error('Bad authKeyId'); } msgId = reader.readLong(); - if (msgId.eq(BigInt(0))) { + if (msgId === 0n) { throw new Error('Bad msgId'); } /** ^ We should make sure that the read ``msg_id`` is greater diff --git a/src/lib/gramjs/network/MTProtoSender.ts b/src/lib/gramjs/network/MTProtoSender.ts index c3b3dbd4a..a58863945 100644 --- a/src/lib/gramjs/network/MTProtoSender.ts +++ b/src/lib/gramjs/network/MTProtoSender.ts @@ -1,5 +1,3 @@ -import type BigInt from 'big-integer'; - import type { TLMessage } from '../tl/core'; import { RPCError, RPCMessageToError } from '../errors'; @@ -18,7 +16,7 @@ import { BadMessageError, InvalidBufferError, SecurityError, TypeNotFoundError, } from '../errors/Common'; import PendingState from '../extensions/PendingState'; -import { sleep } from '../Helpers'; +import { jsonStringifyWithBigInt, sleep } from '../Helpers'; import MessageContainer from '../tl/core/MessageContainer'; import { doAuthentication } from './Authenticator'; import MtProtoPlainSender from './MTProtoPlainSender'; @@ -823,16 +821,16 @@ export default class MTProtoSender { * @returns {*[]} * @private */ - _popStates(msgId: BigInt.BigInteger) { + _popStates(msgId: bigint) { const state = this._pendingState.getAndDelete(msgId); if (state) { return [state]; } - const toPop: BigInt.BigInteger[] = []; + const toPop: bigint[] = []; for (const pendingState of this._pendingState.values()) { - if (pendingState.containerId?.equals(msgId)) { + if (pendingState.containerId === msgId) { toPop.push(pendingState.msgId!); } } @@ -1005,7 +1003,7 @@ export default class MTProtoSender { _handleBadNotification(message: TLMessage) { const badMsg = message.obj; const states = this._popStates(badMsg.badMsgId); - this._log.debug(`Handling bad msg ${JSON.stringify(badMsg)}`); + this._log.debug(`Handling bad msg ${jsonStringifyWithBigInt(badMsg)}`); if ([16, 17].includes(badMsg.errorCode)) { // Sent msg_id too low or too high (respectively). // Use the current msg_id to determine the right time offset. diff --git a/src/lib/gramjs/network/MTProtoState.ts b/src/lib/gramjs/network/MTProtoState.ts index 001b2a5ca..1bc76ec30 100644 --- a/src/lib/gramjs/network/MTProtoState.ts +++ b/src/lib/gramjs/network/MTProtoState.ts @@ -1,5 +1,3 @@ -import BigInt from 'big-integer'; - import type { AuthKey } from '../crypto/AuthKey'; import { CTR } from '../crypto/CTR'; @@ -27,9 +25,9 @@ export default class MTProtoState { timeOffset: number; - salt: bigInt.BigInteger; + salt: bigint; - private id: bigInt.BigInteger; + private id: bigint; _sequence: number; @@ -37,7 +35,7 @@ export default class MTProtoState { _isOutgoing: boolean; - private _lastMsgId: bigInt.BigInteger; + private _lastMsgId: bigint; private msgIds: string[]; @@ -74,11 +72,11 @@ export default class MTProtoState { this._isCall = isCall; this._isOutgoing = isOutgoing; this.timeOffset = 0; - this.salt = BigInt.zero; + this.salt = 0n; - this.id = BigInt.zero; + this.id = 0n; this._sequence = 0; - this._lastMsgId = BigInt.zero; + this._lastMsgId = 0n; this.msgIds = []; this.reset(); } @@ -90,7 +88,7 @@ export default class MTProtoState { // Session IDs can be random on every connection this.id = generateRandomLong(true); this._sequence = 0; - this._lastMsgId = BigInt(0); + this._lastMsgId = 0n; this.msgIds = []; } @@ -142,11 +140,13 @@ export default class MTProtoState { * @param contentRelated * @param afterId */ - async writeDataAsMessage(buffer: BinaryWriter, data: Buffer, contentRelated: boolean, afterId?: BigInt.BigInteger) { + async writeDataAsMessage( + buffer: BinaryWriter, data: Buffer, contentRelated: boolean, afterId?: bigint, + ): Promise { const msgId = this._getNewMsgId(); const seqNo = this._getSeqNo(contentRelated); let body; - if (!afterId) { + if (afterId === undefined) { body = await GZIPPacked.gzipIfSmaller(contentRelated, data); } else { // Invoke query expects a query with a getBytes func @@ -185,7 +185,7 @@ export default class MTProtoState { throw new Error('Auth key unset'); } - if (!this.salt || !this.id || !authKey || !this.authKey.keyId) { + if (this.salt === undefined || this.authKey.keyId === undefined) { throw new Error('Unset params'); } @@ -255,7 +255,7 @@ export default class MTProtoState { if (!this._isCall) { const keyId = readBigIntFromBuffer(body.slice(0, 8)); - if (!this.authKey.keyId || keyId.neq(this.authKey.keyId)) { + if (keyId !== this.authKey.keyId) { throw new SecurityError('Server replied with an invalid auth key'); } } @@ -304,7 +304,7 @@ export default class MTProtoState { } else { reader.readLong(); // removeSalt const serverId = reader.readLong(); - if (!serverId.eq(this.id)) { + if (serverId !== this.id) { throw new SecurityError('Server replied with a wrong session ID'); } @@ -358,11 +358,9 @@ export default class MTProtoState { _getNewMsgId() { const now = Date.now() / 1000 + this.timeOffset; const nanoseconds = Math.floor((now - Math.floor(now)) * 1e9); - let newMsgId = (BigInt(Math.floor(now)) - .shiftLeft(BigInt(32))).or(BigInt(nanoseconds) - .shiftLeft(BigInt(2))); - if (this._lastMsgId.greaterOrEquals(newMsgId)) { - newMsgId = this._lastMsgId.add(BigInt(4)); + let newMsgId = (BigInt(Math.floor(now)) << 32n) | (BigInt(nanoseconds) << 2n); + if (this._lastMsgId >= newMsgId) { + newMsgId = this._lastMsgId + 4n; } this._lastMsgId = newMsgId; return newMsgId; @@ -371,28 +369,28 @@ export default class MTProtoState { /** * Returns the understood time by the message id (server time + local offset) */ - getMsgIdTimeLocal(msgId: BigInt.BigInteger) { - if (this._lastMsgId.eq(0)) { + getMsgIdTimeLocal(msgId: bigint) { + if (this._lastMsgId === 0n) { // this means it's the first message sent/received so don't check yet - return false; + return undefined; } - return msgId.shiftRight(BigInt(32)).toJSNumber() - this.timeOffset; + return Number(msgId >> 32n) - this.timeOffset; } /** * Updates the time offset to the correct * one given a known valid message ID. - * @param correctMsgId {BigInteger} + * @param correctMsgId {bigint} */ - updateTimeOffset(correctMsgId: BigInt.BigInteger) { + updateTimeOffset(correctMsgId: bigint) { const bad = this._getNewMsgId(); const old = this.timeOffset; const now = Math.floor(Date.now() / 1000); - const correct = correctMsgId.shiftRight(BigInt(32)).toJSNumber(); + const correct = Number(correctMsgId >> 32n); this.timeOffset = correct - now; if (this.timeOffset !== old) { - this._lastMsgId = BigInt(0); + this._lastMsgId = 0n; this._log.debug( // eslint-disable-next-line @stylistic/max-len `Updated time offset (old offset ${old}, bad ${bad.toString()}, good ${correctMsgId.toString()}, new ${this.timeOffset})`, diff --git a/src/lib/gramjs/network/RequestState.ts b/src/lib/gramjs/network/RequestState.ts index 6597776a4..4b723dc38 100644 --- a/src/lib/gramjs/network/RequestState.ts +++ b/src/lib/gramjs/network/RequestState.ts @@ -1,5 +1,3 @@ -import type BigInt from 'big-integer'; - import type { Api } from '../tl'; import Deferred from '../../../util/Deferred'; @@ -8,9 +6,9 @@ export type CallableRequest = Api.AnyRequest | Api.MsgsAck | Api.MsgsStateInfo | type RequestResponse = T extends { __response: infer R } ? R : void; export default class RequestState { - public containerId?: BigInt.BigInteger; + public containerId?: bigint; - public msgId?: BigInt.BigInteger; + public msgId?: bigint; public request: any; diff --git a/src/lib/gramjs/network/connection/TCPAbridged.ts b/src/lib/gramjs/network/connection/TCPAbridged.ts index 88236d7fe..1c3261524 100644 --- a/src/lib/gramjs/network/connection/TCPAbridged.ts +++ b/src/lib/gramjs/network/connection/TCPAbridged.ts @@ -1,5 +1,3 @@ -import BigInt from 'big-integer'; - import type { PromisedWebSockets } from '../../extensions'; import { readBufferFromBigInt } from '../../Helpers'; diff --git a/src/lib/gramjs/tl/MTProtoRequest.ts b/src/lib/gramjs/tl/MTProtoRequest.ts index fa960d15b..e0c147211 100644 --- a/src/lib/gramjs/tl/MTProtoRequest.ts +++ b/src/lib/gramjs/tl/MTProtoRequest.ts @@ -3,7 +3,7 @@ export abstract class MTProtoRequest { private sequence: number; - private msgId: number; + private msgId: bigint; private readonly dirty: boolean; @@ -19,7 +19,7 @@ export abstract class MTProtoRequest { constructor() { this.sent = false; - this.msgId = 0; // long + this.msgId = 0n; // long this.sequence = 0; this.dirty = false; diff --git a/src/lib/gramjs/tl/api.d.ts b/src/lib/gramjs/tl/api.d.ts index b10bebfe4..3725ac570 100644 --- a/src/lib/gramjs/tl/api.d.ts +++ b/src/lib/gramjs/tl/api.d.ts @@ -1,8 +1,6 @@ // This file is autogenerated. All changes will be overwritten. -import { BigInteger } from 'big-integer'; - export default Api; namespace Api { @@ -21,9 +19,9 @@ namespace Api { type Bool = boolean; type int = number; type double = number; - type int128 = BigInteger; - type int256 = BigInteger; - type long = BigInteger; + type int128 = bigint; + type int256 = bigint; + type long = bigint; type bytes = Buffer; class VirtualClass { diff --git a/src/lib/gramjs/tl/core/RPCResult.ts b/src/lib/gramjs/tl/core/RPCResult.ts index f3afa12f8..622bdd614 100644 --- a/src/lib/gramjs/tl/core/RPCResult.ts +++ b/src/lib/gramjs/tl/core/RPCResult.ts @@ -1,4 +1,3 @@ -import type BigInt from 'big-integer'; import type { BinaryReader } from '../../extensions'; @@ -13,7 +12,7 @@ export default class RPCResult { private CONSTRUCTOR_ID: number; - private reqMsgId: BigInt.BigInteger; + private reqMsgId: bigint; private body?: Buffer; @@ -22,7 +21,7 @@ export default class RPCResult { private classType: string; constructor( - reqMsgId: BigInt.BigInteger, + reqMsgId: bigint, body?: Buffer, error?: Api.RpcError, ) { diff --git a/src/lib/gramjs/tl/core/TLMessage.ts b/src/lib/gramjs/tl/core/TLMessage.ts index 6ae90afae..3efc29149 100644 --- a/src/lib/gramjs/tl/core/TLMessage.ts +++ b/src/lib/gramjs/tl/core/TLMessage.ts @@ -1,17 +1,15 @@ -import type BigInt from 'big-integer'; - export default class TLMessage { static SIZE_OVERHEAD = 12; static classType = 'constructor'; - msgId: BigInt.BigInteger; + msgId: bigint; private seqNo: number; obj: any; - constructor(msgId: bigInt.BigInteger, seqNo: number, obj: any) { + constructor(msgId: bigint, seqNo: number, obj: any) { this.msgId = msgId; this.seqNo = seqNo; this.obj = obj; diff --git a/src/lib/gramjs/tl/types-generator/template.ts b/src/lib/gramjs/tl/types-generator/template.ts index f448c0a29..c7a6d3248 100644 --- a/src/lib/gramjs/tl/types-generator/template.ts +++ b/src/lib/gramjs/tl/types-generator/template.ts @@ -158,8 +158,6 @@ ${indent}}`.trim(); return ` // This file is autogenerated. All changes will be overwritten. -import { BigInteger } from 'big-integer'; - export default Api; namespace Api { @@ -178,9 +176,9 @@ namespace Api { type Bool = boolean; type int = number; type double = number; - type int128 = BigInteger; - type int256 = BigInteger; - type long = BigInteger; + type int128 = bigint; + type int256 = bigint; + type long = bigint; type bytes = Buffer; class VirtualClass { diff --git a/src/lib/gramjs/types.ts b/src/lib/gramjs/types.ts index 7ccd04e27..a43065259 100644 --- a/src/lib/gramjs/types.ts +++ b/src/lib/gramjs/types.ts @@ -8,7 +8,7 @@ export type FullEntity = | Api.ChannelFull; export type EntityLike = - | bigInt.BigInteger + | bigint | string | Api.TypePeer | Api.TypeInputPeer diff --git a/src/util/entities/ids.ts b/src/util/entities/ids.ts index 73ee86148..080b2c712 100644 --- a/src/util/entities/ids.ts +++ b/src/util/entities/ids.ts @@ -1,26 +1,33 @@ import { CHANNEL_ID_BASE } from '../../config'; +import { toJSNumber } from '../numbers'; export function isUserId(entityId: string) { return !entityId.startsWith('-'); } export function isChannelId(entityId: string) { - const n = Number(entityId); + const n = BigInt(entityId); return n < -CHANNEL_ID_BASE; } export function toChannelId(mtpId: string) { - const n = Number(mtpId); + const n = BigInt(mtpId); return String(-CHANNEL_ID_BASE - n); } -export function getCleanPeerId(peerId: string) { - return isChannelId(peerId) - // Remove -1 and leading zeros - ? peerId.replace(/^-10+/, '') - : peerId.replace('-', ''); +export function getRawPeerId(id: string) { + const n = BigInt(id); + if (isUserId(id)) { + return n; + } + + if (isChannelId(id)) { + return -n - CHANNEL_ID_BASE; + } + + return n * -1n; } export function getPeerIdDividend(peerId: string) { - return Math.abs(Number(getCleanPeerId(peerId))); + return toJSNumber(getRawPeerId(peerId)); } diff --git a/src/util/numbers.ts b/src/util/numbers.ts new file mode 100644 index 000000000..e9e8c7b2e --- /dev/null +++ b/src/util/numbers.ts @@ -0,0 +1,27 @@ +import { DEBUG } from '../config'; + +export function toJSNumber(value: undefined): undefined; +export function toJSNumber(value: bigint): number; +export function toJSNumber(value?: bigint): number | undefined; +export function toJSNumber(value?: bigint): number | undefined { + if (value === undefined) return undefined; + + if (DEBUG && (value < Number.MIN_SAFE_INTEGER || value > Number.MAX_SAFE_INTEGER)) { + // eslint-disable-next-line no-console + console.error('Unsafe BigInt conversion', value); + } + + return Number(value); +} + +export function tryParseBigInt(value: string): bigint | undefined { + try { + return BigInt(value); + } catch (error) { + if (DEBUG) { + // eslint-disable-next-line no-console + console.error('Error parsing BigInt', value, error); + } + return undefined; + } +}