From f2b5363bb8c54200dabc83dc674136abc2c19f82 Mon Sep 17 00:00:00 2001 From: morethanwords Date: Mon, 28 Sep 2020 03:23:00 +0300 Subject: [PATCH] Stickers optimizations File reference database Misc --- package-lock.json | 70 - package.json | 12 +- src/components/audio.ts | 2 +- .../emoticonsDropdown/tabs/stickers.ts | 3 +- src/components/sidebarRight/stickers.ts | 2 +- src/helpers/json.ts | 31 + src/layer.d.ts | 211 +- src/lib/appManagers/apiUpdatesManager.ts | 17 +- src/lib/appManagers/appDocsManager.ts | 26 +- src/lib/appManagers/appDownloadManager.ts | 41 +- src/lib/appManagers/appImManager.ts | 7 +- src/lib/appManagers/appMessagesManager.ts | 150 +- src/lib/appManagers/appPhotosManager.ts | 34 +- src/lib/appManagers/appStateManager.ts | 33 +- src/lib/appManagers/appWebPagesManager.ts | 3 +- src/lib/bin_utils.ts | 29 +- src/lib/config.ts | 1 - src/lib/crypto/crypto_utils.ts | 29 +- src/lib/crypto/srp.ts | 37 +- src/lib/lottieLoader.ts | 53 +- src/lib/mediaPlayer.ts | 8 +- src/lib/mtproto/apiFileManager.ts | 1 + src/lib/mtproto/authorizer.ts | 25 +- src/lib/mtproto/networker.ts | 5 +- src/lib/mtproto/referenceDatabase.ts | 64 +- src/lib/polyfill.ts | 12 +- src/lib/rlottie/rlottie.worker.ts | 22 +- src/lib/storage.ts | 24 +- src/lib/utils.ts | 22 + src/scripts/generate_mtproto_types.js | 20 +- src/scripts/in/schema_additional_params.json | 26 + src/scripts/in/schema_replace_types.json | 3 + src/scss/partials/_chat.scss | 29 +- src/scss/partials/_chatBubble.scss | 4 +- src/scss/partials/_chatlist.scss | 16 +- src/scss/partials/_emojiDropdown.scss | 7 +- src/scss/partials/_leftSidebar.scss | 2 +- src/scss/partials/_rightSidebar.scss | 2 +- src/scss/partials/_selector.scss | 2 +- src/scss/partials/_slider.scss | 2 +- src/scss/partials/popups/_popup.scss | 2 +- src/scss/partials/popups/_stickers.scss | 2 +- src/scss/style.scss | 11 +- src/vendor/leemon.ts | 2136 +++++++++++++++++ 44 files changed, 2799 insertions(+), 439 deletions(-) create mode 100644 src/helpers/json.ts create mode 100644 src/scripts/in/schema_replace_types.json create mode 100644 src/vendor/leemon.ts diff --git a/package-lock.json b/package-lock.json index 01bd21c4..db765a05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3177,15 +3177,6 @@ "integrity": "sha512-GdZbRSJ3Cv5fiwT6I0SQ3ckeN2PWNqxd26W9Z2fCK1tGrrasGy4puvNFtnddqH9UJFMQYXxEuuB7B8UK+LLwSg==", "dev": true }, - "@types/puppeteer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-3.0.1.tgz", - "integrity": "sha512-t03eNKCvWJXhQ8wkc5C6GYuSqMEdKLOX0GLMGtks25YZr38wKZlKTwGM/BoAPVtdysX7Bb9tdwrDS1+NrW3RRA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/pvutils": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/@types/pvutils/-/pvutils-0.0.2.tgz", @@ -6461,15 +6452,6 @@ "pako": "^1.0.10" } }, - "fastdom": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/fastdom/-/fastdom-1.0.9.tgz", - "integrity": "sha512-SSp4fbVzu8JkkG01NUX+0iOwe9M5PN3MGIQ84txLf4TkkJG4q30khkzumKgi4hUqO1+jX6wLHfnCPoZ6eSZ6Tg==", - "dev": true, - "requires": { - "strictdom": "^1.0.1" - } - }, "fastparse": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", @@ -6499,28 +6481,6 @@ "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", "dev": true }, - "file-loader": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-4.3.0.tgz", - "integrity": "sha512-aKrYPYjF1yG3oX0kWRrqrSMfgftm7oJW5M+m4owoldH5C51C0RkIwB++JbRvEW3IU6/ZG5n8UvEcdgwOt2UOWA==", - "dev": true, - "requires": { - "loader-utils": "^1.2.3", - "schema-utils": "^2.5.0" - }, - "dependencies": { - "schema-utils": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.2.tgz", - "integrity": "sha512-sazKNMBX/jwrXRkOI7N6dtiTVYqzSckzol8SGuHt0lE/v3xSW6cUkOqzu6Bq2tW+dlUzq3CWIqHU3ZKauliqdg==", - "dev": true, - "requires": { - "ajv": "^6.10.2", - "ajv-keywords": "^3.4.1" - } - } - } - }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -9288,12 +9248,6 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, - "lottie-web": { - "version": "5.6.10", - "resolved": "https://registry.npmjs.org/lottie-web/-/lottie-web-5.6.10.tgz", - "integrity": "sha512-ucTzaiBJOMm56/K7Wqjajyh7qXlonHKB3+b5fHvhSiz+jOrXt6QDNKAinI3qdw/zvvYHKzXvFMy5SgOXbOJ5ng==", - "dev": true - }, "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", @@ -16721,24 +16675,12 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, - "streamsaver": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/streamsaver/-/streamsaver-2.0.4.tgz", - "integrity": "sha512-rGES0zmHIPxinV32H2Dz55qgOE5dXui8yfu+JEjPAI294cDmwX4Oe7tCZLrnhjc/1z7hyIv9H6TMH2kqN2Tdqw==", - "dev": true - }, "strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", "dev": true }, - "strictdom": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strictdom/-/strictdom-1.0.1.tgz", - "integrity": "sha1-GJ3pFkn3PUTVm4Qy76aO+dJllGA=", - "dev": true - }, "string-length": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", @@ -17974,12 +17916,6 @@ "minimalistic-assert": "^1.0.0" } }, - "web-streams-polyfill": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-2.1.1.tgz", - "integrity": "sha512-dlNpL2aab3g8CKfGz6rl8FNmGaRWLLn2g/DtSc9IjB30mEdE6XxzPfPSig5BwGSzI+oLxHyETrQGKjrVVhbLCg==", - "dev": true - }, "webcrypto-core": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.1.6.tgz", @@ -18007,12 +17943,6 @@ "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", "dev": true }, - "webp-hero": { - "version": "0.0.0-dev.24", - "resolved": "https://registry.npmjs.org/webp-hero/-/webp-hero-0.0.0-dev.24.tgz", - "integrity": "sha512-3XG47dRMV36RFwfYLUynOiKDXuqXfQ3dz7yvqGH5VplldV0NvngQzF0qBsZ3vRQwrigekRLeX/NdNwsaGfSnPQ==", - "dev": true - }, "webpack": { "version": "4.43.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz", diff --git a/package.json b/package.json index 85326d76..0c70d1cb 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "start": "node --max-old-space-size=8192 node_modules/webpack-dev-server/bin/webpack-dev-server.js --config webpack.dev.js", + "start": "node --max-old-space-size=12048 node_modules/webpack-dev-server/bin/webpack-dev-server.js --config webpack.dev.js", "start:production": "webpack-dev-server --config webpack.prod.js", "serve": "npm run build; node server.js", "build": "webpack --config webpack.prod.js", @@ -12,7 +12,8 @@ "test": "jest --config=jest.config.js", "profile": "webpack --profile --json > stats.json --config webpack.prod.js", "profile:dev": "webpack --profile --json > stats.json --config webpack.dev.js", - "whybundled": "npm run profile; whybundled stats.json" + "whybundled": "npm run profile && whybundled stats.json", + "generate-mtproto-types": "node ./src/scripts/generate_mtproto_types.js src/" }, "author": "", "license": "ISC", @@ -31,7 +32,6 @@ "@types/aes-js": "^3.1.1", "@types/chrome": "0.0.91", "@types/jest": "^24.9.1", - "@types/puppeteer": "^3.0.1", "@types/serviceworker-webpack-plugin": "^1.0.1", "aes-js": "^3.1.2", "autoprefixer": "^9.8.0", @@ -41,8 +41,6 @@ "css-loader": "^3.5.3", "express": "^4.17.1", "fast-png": "^5.0.2", - "fastdom": "^1.0.9", - "file-loader": "^4.3.0", "handlebars": "^4.7.6", "handlebars-loader": "^1.7.1", "html-webpack-plugin": "^3.2.0", @@ -50,7 +48,6 @@ "install": "^0.13.0", "jest": "^24.9.0", "leemon": "^6.2.0", - "lottie-web": "^5.6.10", "media-query-plugin": "^1.3.1", "mini-css-extract-plugin": "^0.9.0", "node-sass": "^4.14.1", @@ -65,15 +62,12 @@ "resolve-url-loader": "^3.1.1", "sass-loader": "^8.0.2", "serviceworker-webpack-plugin": "^1.0.1", - "streamsaver": "^2.0.4", "style-loader": "^1.2.1", "terser-webpack-plugin": "^3.0.2", "ts-jest": "^24.3.0", "ts-loader": "^6.2.2", "typescript": "^3.9.3", "url-loader": "^2.3.0", - "web-streams-polyfill": "^2.1.1", - "webp-hero": "0.0.0-dev.24", "webpack": "^4.43.0", "webpack-cli": "^3.3.11", "webpack-merge": "^4.2.2", diff --git a/src/components/audio.ts b/src/components/audio.ts index df409093..20a9bb70 100644 --- a/src/components/audio.ts +++ b/src/components/audio.ts @@ -62,7 +62,7 @@ function wrapVoiceMessage(doc: MyDocument, audioEl: AudioElement) { timeDiv.classList.add('audio-time'); audioEl.append(svg, timeDiv); - let waveform = (doc.attributes.find(attribute => attribute._ == 'documentAttributeAudio') as DocumentAttribute.documentAttributeAudio).waveform || []; + let waveform = (doc.attributes.find(attribute => attribute._ == 'documentAttributeAudio') as DocumentAttribute.documentAttributeAudio).waveform || new Uint8Array([]); waveform = decodeWaveform(waveform.slice()); //console.log('decoded waveform:', waveform); diff --git a/src/components/emoticonsDropdown/tabs/stickers.ts b/src/components/emoticonsDropdown/tabs/stickers.ts index 1eb032c5..da0a87ec 100644 --- a/src/components/emoticonsDropdown/tabs/stickers.ts +++ b/src/components/emoticonsDropdown/tabs/stickers.ts @@ -147,7 +147,8 @@ export default class StickersTab implements EmoticonsTab { autoplay: false, animationData: json, width: 32, - height: 32 + height: 32, + needUpscale: true }, EMOTICONSSTICKERGROUP); }); } else { diff --git a/src/components/sidebarRight/stickers.ts b/src/components/sidebarRight/stickers.ts index 8d90f2e5..556ca5c7 100644 --- a/src/components/sidebarRight/stickers.ts +++ b/src/components/sidebarRight/stickers.ts @@ -10,7 +10,7 @@ import animationIntersector from "../animationIntersector"; import { RichTextProcessor } from "../../lib/richtextprocessor"; import { wrapSticker } from "../wrappers"; import appSidebarRight, { AppSidebarRight } from "../../lib/appManagers/appSidebarRight"; -import { StickerSet, StickerSetCovered, Document } from "../../layer"; +import { StickerSet, StickerSetCovered } from "../../layer"; export default class AppStickersTab implements SliderTab { private container = document.getElementById('stickers-container') as HTMLDivElement; diff --git a/src/helpers/json.ts b/src/helpers/json.ts new file mode 100644 index 00000000..53926f5a --- /dev/null +++ b/src/helpers/json.ts @@ -0,0 +1,31 @@ +export function parse(text: string) { + let arr: number[] = [], performedValue: any = null; + return JSON.parse(text, (key, value) => { + //console.log(key, value); + if(key == 'type' && value == 'bytes') { + arr = []; + return undefined; + } else if(arr) { + if(key == 'value') { + performedValue = new Uint8Array(arr); + arr = null; + return undefined; + } else arr[+key] = value; + } else if(performedValue) { + const v = performedValue; + performedValue = null; + return v; + } + + return value; + }); +} +// parse('{"file_reference": {"type": "bytes", "value": [1,2,3]}, "file_reference2": {"type": "bytes", "value": [3,2,1]}}'); +// -> {file_reference: Uint8Array} + +export function stringify(value: any) { + return JSON.stringify(value, (key, value) => { + if(key == 'downloaded' || (key == 'url' && value.indexOf('blob:') === 0)) return undefined; + return value; + }); +} \ No newline at end of file diff --git a/src/layer.d.ts b/src/layer.d.ts index bb3fd247..6640dc0d 100644 --- a/src/layer.d.ts +++ b/src/layer.d.ts @@ -223,7 +223,7 @@ export namespace InputMedia { description: string, photo?: InputWebDocument, invoice: Invoice, - payload: Uint8Array | number[], + payload: Uint8Array, provider: string, provider_data: DataJSON, start_param: string @@ -243,7 +243,7 @@ export namespace InputMedia { _: 'inputMediaPoll', flags?: number, poll: Poll, - correct_answers?: Array, + correct_answers?: Array, solution?: string, solution_entities?: Array }; @@ -770,7 +770,8 @@ export type Message = Message.messageEmpty | Message.message | Message.messageSe export namespace Message { export type messageEmpty = { _: 'messageEmpty', - id: number + id: number, + deleted?: boolean }; export type message = { @@ -785,6 +786,7 @@ export namespace Message { from_scheduled?: true, legacy?: true, edit_hide?: true, + unread?: true, }>, id: number, from_id?: number, @@ -801,7 +803,12 @@ export namespace Message { edit_date?: number, post_author?: string, grouped_id?: string, - restriction_reason?: Array + restriction_reason?: Array, + mid?: number, + deleted?: boolean, + peerID?: number, + fromID?: number, + canBeEdited?: boolean }; export type messageService = { @@ -814,13 +821,19 @@ export namespace Message { silent?: true, post?: true, legacy?: true, + unread?: true, }>, id: number, from_id?: number, to_id: Peer, reply_to_msg_id?: number, date: number, - action: MessageAction + action: MessageAction, + mid?: number, + deleted?: boolean, + peerID?: number, + fromID?: number, + canBeEdited?: boolean }; } @@ -1001,7 +1014,7 @@ export namespace MessageAction { flags?: number, currency: string, total_amount: string, - payload: Uint8Array | number[], + payload: Uint8Array, info?: PaymentRequestedInfo, shipping_option_id?: string, charge: PaymentCharge @@ -1155,14 +1168,14 @@ export namespace PhotoSize { location: FileLocation, w: number, h: number, - bytes: Uint8Array | number[], + bytes: Uint8Array, url?: string }; export type photoStrippedSize = { _: 'photoStrippedSize', type: string, - bytes: Uint8Array | number[], + bytes: Uint8Array, url?: string }; } @@ -1230,7 +1243,7 @@ export namespace AuthExportedAuthorization { export type authExportedAuthorization = { _: 'auth.exportedAuthorization', id: number, - bytes: Uint8Array | number[] + bytes: Uint8Array }; } @@ -2003,7 +2016,7 @@ export namespace Update { peer: Peer, msg_id: number, chat_instance: string, - data?: Uint8Array | number[], + data?: Uint8Array, game_short_name?: string }; @@ -2021,7 +2034,7 @@ export namespace Update { user_id: number, msg_id: InputBotInlineMessageID, chat_instance: string, - data?: Uint8Array | number[], + data?: Uint8Array, game_short_name?: string }; @@ -2094,7 +2107,7 @@ export namespace Update { _: 'updateBotShippingQuery', query_id: string, user_id: number, - payload: Uint8Array | number[], + payload: Uint8Array, shipping_address: PostAddress }; @@ -2103,7 +2116,7 @@ export namespace Update { flags?: number, query_id: string, user_id: number, - payload: Uint8Array | number[], + payload: Uint8Array, info?: PaymentRequestedInfo, shipping_option_id?: string, currency: string, @@ -2230,7 +2243,7 @@ export namespace Update { _: 'updateMessagePollVote', poll_id: string, user_id: number, - options: Array + options: Array }; export type updateDialogFilter = { @@ -2440,15 +2453,15 @@ export namespace UploadFile { _: 'upload.file', type: StorageFileType, mtime: number, - bytes: Uint8Array | number[] + bytes: Uint8Array }; export type uploadFileCdnRedirect = { _: 'upload.fileCdnRedirect', dc_id: number, - file_token: Uint8Array | number[], - encryption_key: Uint8Array | number[], - encryption_iv: Uint8Array | number[], + file_token: Uint8Array, + encryption_key: Uint8Array, + encryption_iv: Uint8Array, file_hashes: Array }; } @@ -2472,7 +2485,7 @@ export namespace DcOption { id: number, ip_address: string, port: number, - secret?: Uint8Array | number[] + secret?: Uint8Array }; } @@ -2619,7 +2632,7 @@ export namespace EncryptedChat { date: number, admin_id: number, participant_id: number, - g_a: Uint8Array | number[] + g_a: Uint8Array }; export type encryptedChat = { @@ -2629,7 +2642,7 @@ export namespace EncryptedChat { date: number, admin_id: number, participant_id: number, - g_a_or_b: Uint8Array | number[], + g_a_or_b: Uint8Array, key_fingerprint: string }; @@ -2715,7 +2728,7 @@ export namespace EncryptedMessage { random_id: string, chat_id: number, date: number, - bytes: Uint8Array | number[], + bytes: Uint8Array, file: EncryptedFile }; @@ -2724,7 +2737,7 @@ export namespace EncryptedMessage { random_id: string, chat_id: number, date: number, - bytes: Uint8Array | number[] + bytes: Uint8Array }; } @@ -2736,15 +2749,15 @@ export type MessagesDhConfig = MessagesDhConfig.messagesDhConfigNotModified | Me export namespace MessagesDhConfig { export type messagesDhConfigNotModified = { _: 'messages.dhConfigNotModified', - random: Uint8Array | number[] + random: Uint8Array }; export type messagesDhConfig = { _: 'messages.dhConfig', g: number, - p: Uint8Array | number[], + p: Uint8Array, version: number, - random: Uint8Array | number[] + random: Uint8Array }; } @@ -3183,7 +3196,7 @@ export namespace DocumentAttribute { duration: number, title?: string, performer?: string, - waveform?: Uint8Array | number[] + waveform?: Uint8Array }; export type documentAttributeFilename = { @@ -3359,13 +3372,13 @@ export namespace AccountPassword { has_password?: true, }>, current_algo?: PasswordKdfAlgo, - srp_B?: Uint8Array | number[], + srp_B?: Uint8Array, srp_id?: string, hint?: string, email_unconfirmed_pattern?: string, new_algo: PasswordKdfAlgo, new_secure_algo: SecurePasswordKdfAlgo, - secure_random: Uint8Array | number[] + secure_random: Uint8Array }; } @@ -3393,7 +3406,7 @@ export namespace AccountPasswordInputSettings { _: 'account.passwordInputSettings', flags?: number, new_algo?: PasswordKdfAlgo, - new_password_hash?: Uint8Array | number[], + new_password_hash?: Uint8Array, hint?: string, email?: string, new_secure_settings?: SecureSecretSettings @@ -3587,7 +3600,7 @@ export namespace KeyboardButton { export type keyboardButtonCallback = { _: 'keyboardButtonCallback', text: string, - data: Uint8Array | number[] + data: Uint8Array }; export type keyboardButtonRequestPhone = { @@ -5307,7 +5320,7 @@ export namespace UploadWebFile { mime_type: string, file_type: StorageFileType, mtime: number, - bytes: Uint8Array | number[] + bytes: Uint8Array }; } @@ -5414,7 +5427,7 @@ export namespace InputPaymentCredentials { export type inputPaymentCredentialsSaved = { _: 'inputPaymentCredentialsSaved', id: string, - tmp_password: Uint8Array | number[] + tmp_password: Uint8Array }; export type inputPaymentCredentials = { @@ -5446,7 +5459,7 @@ export type AccountTmpPassword = AccountTmpPassword.accountTmpPassword; export namespace AccountTmpPassword { export type accountTmpPassword = { _: 'account.tmpPassword', - tmp_password: Uint8Array | number[], + tmp_password: Uint8Array, valid_until: number }; } @@ -5530,7 +5543,7 @@ export namespace PhoneCall { date: number, admin_id: number, participant_id: number, - g_a_hash: Uint8Array | number[], + g_a_hash: Uint8Array, protocol: PhoneCallProtocol }; @@ -5545,7 +5558,7 @@ export namespace PhoneCall { date: number, admin_id: number, participant_id: number, - g_b: Uint8Array | number[], + g_b: Uint8Array, protocol: PhoneCallProtocol }; @@ -5560,7 +5573,7 @@ export namespace PhoneCall { date: number, admin_id: number, participant_id: number, - g_a_or_b: Uint8Array | number[], + g_a_or_b: Uint8Array, key_fingerprint: string, protocol: PhoneCallProtocol, connections: Array, @@ -5593,7 +5606,7 @@ export namespace PhoneConnection { ip: string, ipv6: string, port: number, - peer_tag: Uint8Array | number[] + peer_tag: Uint8Array }; } @@ -5637,12 +5650,12 @@ export type UploadCdnFile = UploadCdnFile.uploadCdnFileReuploadNeeded | UploadCd export namespace UploadCdnFile { export type uploadCdnFileReuploadNeeded = { _: 'upload.cdnFileReuploadNeeded', - request_token: Uint8Array | number[] + request_token: Uint8Array }; export type uploadCdnFile = { _: 'upload.cdnFile', - bytes: Uint8Array | number[] + bytes: Uint8Array }; } @@ -6132,7 +6145,7 @@ export namespace FileHash { _: 'fileHash', offset: number, limit: number, - hash: Uint8Array | number[] + hash: Uint8Array }; } @@ -6178,8 +6191,8 @@ export namespace InputSecureFile { id: string, parts: number, md5_checksum: string, - file_hash: Uint8Array | number[], - secret: Uint8Array | number[] + file_hash: Uint8Array, + secret: Uint8Array }; export type inputSecureFile = { @@ -6206,8 +6219,8 @@ export namespace SecureFile { size: number, dc_id: number, date: number, - file_hash: Uint8Array | number[], - secret: Uint8Array | number[] + file_hash: Uint8Array, + secret: Uint8Array }; } @@ -6219,9 +6232,9 @@ export type SecureData = SecureData.secureData; export namespace SecureData { export type secureData = { _: 'secureData', - data: Uint8Array | number[], - data_hash: Uint8Array | number[], - secret: Uint8Array | number[] + data: Uint8Array, + data_hash: Uint8Array, + secret: Uint8Array }; } @@ -6318,7 +6331,7 @@ export namespace SecureValue { translation?: Array, files?: Array, plain_data?: SecurePlainData, - hash: Uint8Array | number[] + hash: Uint8Array }; } @@ -6351,7 +6364,7 @@ export namespace SecureValueHash { export type secureValueHash = { _: 'secureValueHash', type: SecureValueType, - hash: Uint8Array | number[] + hash: Uint8Array }; } @@ -6364,7 +6377,7 @@ export namespace SecureValueError { export type secureValueErrorData = { _: 'secureValueErrorData', type: SecureValueType, - data_hash: Uint8Array | number[], + data_hash: Uint8Array, field: string, text: string }; @@ -6372,56 +6385,56 @@ export namespace SecureValueError { export type secureValueErrorFrontSide = { _: 'secureValueErrorFrontSide', type: SecureValueType, - file_hash: Uint8Array | number[], + file_hash: Uint8Array, text: string }; export type secureValueErrorReverseSide = { _: 'secureValueErrorReverseSide', type: SecureValueType, - file_hash: Uint8Array | number[], + file_hash: Uint8Array, text: string }; export type secureValueErrorSelfie = { _: 'secureValueErrorSelfie', type: SecureValueType, - file_hash: Uint8Array | number[], + file_hash: Uint8Array, text: string }; export type secureValueErrorFile = { _: 'secureValueErrorFile', type: SecureValueType, - file_hash: Uint8Array | number[], + file_hash: Uint8Array, text: string }; export type secureValueErrorFiles = { _: 'secureValueErrorFiles', type: SecureValueType, - file_hash: Array, + file_hash: Array, text: string }; export type secureValueError = { _: 'secureValueError', type: SecureValueType, - hash: Uint8Array | number[], + hash: Uint8Array, text: string }; export type secureValueErrorTranslationFile = { _: 'secureValueErrorTranslationFile', type: SecureValueType, - file_hash: Uint8Array | number[], + file_hash: Uint8Array, text: string }; export type secureValueErrorTranslationFiles = { _: 'secureValueErrorTranslationFiles', type: SecureValueType, - file_hash: Array, + file_hash: Array, text: string }; } @@ -6434,9 +6447,9 @@ export type SecureCredentialsEncrypted = SecureCredentialsEncrypted.secureCreden export namespace SecureCredentialsEncrypted { export type secureCredentialsEncrypted = { _: 'secureCredentialsEncrypted', - data: Uint8Array | number[], - hash: Uint8Array | number[], - secret: Uint8Array | number[] + data: Uint8Array, + hash: Uint8Array, + secret: Uint8Array }; } @@ -6530,10 +6543,10 @@ export namespace PasswordKdfAlgo { export type passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow = { _: 'passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow', - salt1: Uint8Array | number[], - salt2: Uint8Array | number[], + salt1: Uint8Array, + salt2: Uint8Array, g: number, - p: Uint8Array | number[] + p: Uint8Array }; } @@ -6549,12 +6562,12 @@ export namespace SecurePasswordKdfAlgo { export type securePasswordKdfAlgoPBKDF2HMACSHA512iter100000 = { _: 'securePasswordKdfAlgoPBKDF2HMACSHA512iter100000', - salt: Uint8Array | number[] + salt: Uint8Array }; export type securePasswordKdfAlgoSHA512 = { _: 'securePasswordKdfAlgoSHA512', - salt: Uint8Array | number[] + salt: Uint8Array }; } @@ -6567,7 +6580,7 @@ export namespace SecureSecretSettings { export type secureSecretSettings = { _: 'secureSecretSettings', secure_algo: SecurePasswordKdfAlgo, - secure_secret: Uint8Array | number[], + secure_secret: Uint8Array, secure_secret_id: string }; } @@ -6585,8 +6598,8 @@ export namespace InputCheckPasswordSRP { export type inputCheckPasswordSRP = { _: 'inputCheckPasswordSRP', srp_id: string, - A: Uint8Array | number[], - M1: Uint8Array | number[] + A: Uint8Array, + M1: Uint8Array }; } @@ -6858,7 +6871,7 @@ export namespace PollAnswer { export type pollAnswer = { _: 'pollAnswer', text: string, - option: Uint8Array | number[] + option: Uint8Array }; } @@ -6898,7 +6911,7 @@ export namespace PollAnswerVoters { chosen?: true, correct?: true, }>, - option: Uint8Array | number[], + option: Uint8Array, voters: number }; } @@ -7385,13 +7398,13 @@ export namespace AuthLoginToken { export type authLoginToken = { _: 'auth.loginToken', expires: number, - token: Uint8Array | number[] + token: Uint8Array }; export type authLoginTokenMigrateTo = { _: 'auth.loginTokenMigrateTo', dc_id: number, - token: Uint8Array | number[] + token: Uint8Array }; export type authLoginTokenSuccess = { @@ -7515,7 +7528,7 @@ export namespace MessageUserVote { export type messageUserVote = { _: 'messageUserVote', user_id: number, - option: Uint8Array | number[], + option: Uint8Array, date: number }; @@ -7528,7 +7541,7 @@ export namespace MessageUserVote { export type messageUserVoteMultiple = { _: 'messageUserVoteMultiple', user_id: number, - options: Array, + options: Array, date: number }; } @@ -8645,14 +8658,14 @@ export type AuthExportAuthorization = { export type AuthImportAuthorization = { id: number, - bytes: Uint8Array | number[] + bytes: Uint8Array }; export type AuthBindTempAuthKey = { perm_auth_key_id: string, nonce: string, expires_at: number, - encrypted_message: Uint8Array | number[] + encrypted_message: Uint8Array }; export type AuthImportBotAuthorization = { @@ -8695,11 +8708,11 @@ export type AuthExportLoginToken = { }; export type AuthImportLoginToken = { - token: Uint8Array | number[] + token: Uint8Array }; export type AuthAcceptLoginToken = { - token: Uint8Array | number[] + token: Uint8Array }; export type AccountRegisterDevice = { @@ -8708,7 +8721,7 @@ export type AccountRegisterDevice = { token_type: number, token: string, app_sandbox: boolean, - secret: Uint8Array | number[], + secret: Uint8Array, other_uids: Array }; @@ -9314,12 +9327,12 @@ export type MessagesGetDhConfig = { export type MessagesRequestEncryption = { user_id: InputUser, random_id: number, - g_a: Uint8Array | number[] + g_a: Uint8Array }; export type MessagesAcceptEncryption = { peer: InputEncryptedChat, - g_b: Uint8Array | number[], + g_b: Uint8Array, key_fingerprint: string }; @@ -9340,20 +9353,20 @@ export type MessagesReadEncryptedHistory = { export type MessagesSendEncrypted = { peer: InputEncryptedChat, random_id: string, - data: Uint8Array | number[] + data: Uint8Array }; export type MessagesSendEncryptedFile = { peer: InputEncryptedChat, random_id: string, - data: Uint8Array | number[], + data: Uint8Array, file: InputEncryptedFile }; export type MessagesSendEncryptedService = { peer: InputEncryptedChat, random_id: string, - data: Uint8Array | number[] + data: Uint8Array }; export type MessagesReceivedQueue = { @@ -9448,7 +9461,7 @@ export type MessagesReorderStickerSets = { }; export type MessagesGetDocumentByHash = { - sha256: Uint8Array | number[], + sha256: Uint8Array, size: number, mime_type: string }; @@ -9533,7 +9546,7 @@ export type MessagesGetBotCallbackAnswer = { game?: true, peer: InputPeer, msg_id: number, - data?: Uint8Array | number[] + data?: Uint8Array }; export type MessagesSetBotCallbackAnswer = { @@ -9769,7 +9782,7 @@ export type MessagesUpdatePinnedMessage = { export type MessagesSendVote = { peer: InputPeer, msg_id: number, - options: Array + options: Array }; export type MessagesGetPollResults = { @@ -9862,7 +9875,7 @@ export type MessagesGetPollVotes = { flags?: number, peer: InputPeer, id: number, - option?: Uint8Array | number[], + option?: Uint8Array, offset?: string, limit: number }; @@ -9942,7 +9955,7 @@ export type PhotosGetUserPhotos = { export type UploadSaveFilePart = { file_id: string, file_part: number, - bytes: Uint8Array | number[] + bytes: Uint8Array }; export type UploadGetFile = { @@ -9958,7 +9971,7 @@ export type UploadSaveBigFilePart = { file_id: string, file_part: number, file_total_parts: number, - bytes: Uint8Array | number[] + bytes: Uint8Array }; export type UploadGetWebFile = { @@ -9968,18 +9981,18 @@ export type UploadGetWebFile = { }; export type UploadGetCdnFile = { - file_token: Uint8Array | number[], + file_token: Uint8Array, offset: number, limit: number }; export type UploadReuploadCdnFile = { - file_token: Uint8Array | number[], - request_token: Uint8Array | number[] + file_token: Uint8Array, + request_token: Uint8Array }; export type UploadGetCdnFileHashes = { - file_token: Uint8Array | number[], + file_token: Uint8Array, offset: number }; @@ -10350,19 +10363,19 @@ export type PhoneRequestCall = { video?: true, user_id: InputUser, random_id: number, - g_a_hash: Uint8Array | number[], + g_a_hash: Uint8Array, protocol: PhoneCallProtocol }; export type PhoneAcceptCall = { peer: InputPhoneCall, - g_b: Uint8Array | number[], + g_b: Uint8Array, protocol: PhoneCallProtocol }; export type PhoneConfirmCall = { peer: InputPhoneCall, - g_a: Uint8Array | number[], + g_a: Uint8Array, key_fingerprint: string, protocol: PhoneCallProtocol }; diff --git a/src/lib/appManagers/apiUpdatesManager.ts b/src/lib/appManagers/apiUpdatesManager.ts index 0c204509..a2130c89 100644 --- a/src/lib/appManagers/apiUpdatesManager.ts +++ b/src/lib/appManagers/apiUpdatesManager.ts @@ -6,7 +6,6 @@ import appPeersManager from "./appPeersManager"; import appUsersManager from "./appUsersManager"; import appChatsManager from "./appChatsManager"; import { logger, LogLevels } from '../logger'; -import { Updates, UpdatesState } from '../../layer'; export class ApiUpdatesManager { public updatesState: { @@ -63,7 +62,7 @@ export class ApiUpdatesManager { return true; } - public popPendingPtsUpdate(channelID: any) { + public popPendingPtsUpdate(channelID: number) { var curState = channelID ? this.getChannelState(channelID) : this.updatesState; if(!curState.pendingPtsUpdates.length) { return false; @@ -216,7 +215,7 @@ export class ApiUpdatesManager { // Should be first because of updateMessageID // this.log('applying', differenceResult.other_updates.length, 'other updates') - differenceResult.other_updates.forEach((update: any) => { + differenceResult.other_updates.forEach((update) => { switch(update._) { case 'updateChannelTooLong': case 'updateNewChannelMessage': @@ -229,7 +228,7 @@ export class ApiUpdatesManager { }); // this.log('applying', differenceResult.new_messages.length, 'new messages') - differenceResult.new_messages.forEach((apiMessage: any) => { + differenceResult.new_messages.forEach((apiMessage) => { this.saveUpdate({ _: 'updateNewMessage', message: apiMessage, @@ -303,13 +302,13 @@ export class ApiUpdatesManager { appChatsManager.saveApiChats(differenceResult.chats); // Should be first because of updateMessageID - this.log('applying', differenceResult.other_updates.length, 'channel other updates') - differenceResult.other_updates.forEach((update: any) => { + this.log('applying', differenceResult.other_updates.length, 'channel other updates'); + differenceResult.other_updates.forEach((update) => { this.saveUpdate(update); }); - this.log('applying', differenceResult.new_messages.length, 'channel new messages') - differenceResult.new_messages.forEach((apiMessage: any) => { + this.log('applying', differenceResult.new_messages.length, 'channel new messages'); + differenceResult.new_messages.forEach((apiMessage) => { this.saveUpdate({ _: 'updateNewChannelMessage', message: apiMessage, @@ -519,7 +518,7 @@ export class ApiUpdatesManager { apiManager.setUpdatesProcessor(this.processUpdateMessage.bind(this)); if(!state || !state.pts || !state.date || !state.seq) { - apiManager.invokeApi('updates.getState', {}, {noErrorBox: true}).then((stateResult: any) => { + apiManager.invokeApi('updates.getState', {}, {noErrorBox: true}).then((stateResult) => { this.updatesState.seq = stateResult.seq; this.updatesState.pts = stateResult.pts; this.updatesState.date = stateResult.date; diff --git a/src/lib/appManagers/appDocsManager.ts b/src/lib/appManagers/appDocsManager.ts index 3f727d04..65ffab74 100644 --- a/src/lib/appManagers/appDocsManager.ts +++ b/src/lib/appManagers/appDocsManager.ts @@ -1,11 +1,12 @@ import {RichTextProcessor} from '../richtextprocessor'; -import { isObject, getFileURL, FileURLType } from '../utils'; +import { isObject, getFileURL, FileURLType, safeReplaceArrayInObject } from '../utils'; import opusDecodeController from '../opusDecodeController'; import { getFileNameByLocation } from '../bin_utils'; import appDownloadManager, { DownloadBlob } from './appDownloadManager'; import appPhotosManager from './appPhotosManager'; import { isServiceWorkerSupported } from '../config'; import { InputFileLocation, Document, PhotoSize } from '../../layer'; +import referenceDatabase, { ReferenceContext } from '../mtproto/referenceDatabase'; export type MyDocument = Document.document; @@ -14,38 +15,35 @@ export type MyDocument = Document.document; class AppDocsManager { private docs: {[docID: string]: MyDocument} = {}; - public saveDoc(doc: Document, context?: any): MyDocument { + public saveDoc(doc: Document, context?: ReferenceContext): MyDocument { if(doc._ == 'documentEmpty') { return undefined; } + + const oldDoc = this.docs[doc.id]; + + safeReplaceArrayInObject('file_reference', oldDoc, doc); + referenceDatabase.saveContext(doc.file_reference, context); //console.log('saveDoc', apiDoc, this.docs[apiDoc.id]); - if(this.docs[doc.id]) { - const d = this.docs[doc.id]; - + if(oldDoc) { //if(doc._ != 'documentEmpty' && doc._ == d._) { if(doc.thumbs) { - if(!d.thumbs) d.thumbs = doc.thumbs; + if(!oldDoc.thumbs) oldDoc.thumbs = doc.thumbs; /* else if(apiDoc.thumbs[0].bytes && !d.thumbs[0].bytes) { d.thumbs.unshift(apiDoc.thumbs[0]); } else if(d.thumbs[0].url) { // fix for converted thumb in safari apiDoc.thumbs[0] = d.thumbs[0]; } */ } - - d.file_reference = doc.file_reference; + //} - - return d; + return oldDoc; //return Object.assign(d, apiDoc, context); //return context ? Object.assign(d, context) : d; } - - if(context) { - Object.assign(doc, context); - } this.docs[doc.id] = doc; diff --git a/src/lib/appManagers/appDownloadManager.ts b/src/lib/appManagers/appDownloadManager.ts index 4d8e0efd..a8962c6b 100644 --- a/src/lib/appManagers/appDownloadManager.ts +++ b/src/lib/appManagers/appDownloadManager.ts @@ -4,6 +4,8 @@ import { deferredPromise, CancellablePromise } from "../../helpers/cancellablePr import type { DownloadOptions } from "../mtproto/apiFileManager"; import { getFileNameByLocation } from "../bin_utils"; import { InputFile } from "../../layer"; +import referenceDatabase, {ReferenceBytes} from "../mtproto/referenceDatabase"; +import appMessagesManager from "./appMessagesManager"; export type ResponseMethodBlob = 'blob'; export type ResponseMethodJson = 'json'; @@ -74,7 +76,44 @@ export class AppDownloadManager { if(this.downloads.hasOwnProperty(fileName)) return this.downloads[fileName]; const deferred = this.getNewDeferred(fileName); - apiManager.downloadFile(options).then(deferred.resolve, deferred.reject); + + const onError = (err: any) => { + switch(err.type) { + case 'FILE_REFERENCE_EXPIRED': { + // @ts-ignore + const bytes: ReferenceBytes = options?.location?.file_reference; + if(bytes) { + const context = referenceDatabase.getContext(bytes); + switch(context?.type) { + case 'message': { + return appMessagesManager.wrapSingleMessage(context.messageID, true).then(() => { + //console.log('FILE_REFERENCE_EXPIRED: got message', context, options, appMessagesManager.getMessage(context.messageID).media); + return tryDownload(); + }); + } + + default: { + console.warn('FILE_REFERENCE_EXPIRED: not implemented context', context); + } + } + } else { + console.warn('FILE_REFERENCE_EXPIRED: no context for bytes:', bytes); + } + + break; + } + + default: + deferred.reject(err); + break; + } + }; + + const tryDownload = (): Promise => { + return apiManager.downloadFile(options).then(deferred.resolve, onError); + }; + + tryDownload(); //console.log('Will download file:', fileName, url); return deferred; diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index b06b60e6..db3212b5 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -563,7 +563,8 @@ export class AppImManager { cancelEvent(e); if(mediaSizes.isMobile) { - this.setPeer(0); + //this.setPeer(0); + this.selectTab(0); } else { const isNowOpen = document.body.classList.toggle(LEFT_COLUMN_ACTIVE_CLASSNAME); @@ -1056,6 +1057,10 @@ export class AppImManager { } const samePeer = this.peerID == peerID; + + if(samePeer) { // fix for returning back + this.selectTab(1); + } if(this.setPeerPromise && samePeer) return this.setPeerPromise; diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index bb248b5b..cb47d577 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -24,10 +24,12 @@ import { Modify } from "../../types"; import { logger, LogLevels } from "../logger"; import type {ApiFileManager} from '../mtproto/apiFileManager'; import appDownloadManager from "./appDownloadManager"; -import { DialogFilter, InputDialogPeer, InputMessage, MethodDeclMap, MessagesFilter, PhotoSize, DocumentAttribute, Dialog as MTDialog, MessagesDialogs, MessagesPeerDialogs } from "../../layer"; +import { DialogFilter, Message, InputMessage, MethodDeclMap, MessagesFilter, PhotoSize, DocumentAttribute, Dialog as MTDialog, MessagesDialogs, MessagesPeerDialogs, MessagesMessages, MessageMedia } from "../../layer"; +import referenceDatabase, { ReferenceContext } from "../mtproto/referenceDatabase"; //console.trace('include'); // TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет +// TODO: если удалить диалог находясь в папке, то он не удалится из папки и будет виден в настройках const APITIMEOUT = 0; @@ -135,7 +137,7 @@ export class DialogsStorage { const mid = appMessagesIDsManager.getFullMessageID(dialog.top_message, channelID); const message = appMessagesManager.getMessage(mid); - let topDate = message.date; + let topDate = (message as Message.message).date || Date.now() / 1000; if(channelID) { const channel = appChatsManager.getChat(channelID); if(!topDate || channel.date && channel.date > topDate) { @@ -458,8 +460,10 @@ export class FiltersStorage { } } +type MyMessage = Message.message | Message.messageService; + export class AppMessagesManager { - public messagesStorage: any = {}; + public messagesStorage: {[mid: string]: any} = {}; public groupedMessagesStorage: {[groupID: string]: any} = {}; // will be used for albums public historiesStorage: { [peerID: string]: HistoryStorage @@ -477,8 +481,7 @@ export class AppMessagesManager { public lastSearchResults: any = []; public needSingleMessages: number[] = []; - public fetchSingleMessagesTimeout = 0; - private fetchSingleMessagesPromise: Promise = null; + private fetchSingleMessagesPromise: Promise = null; public maxSeenID = 0; @@ -517,9 +520,9 @@ export class AppMessagesManager { $rootScope.$on('webpage_updated', (e) => { let eventData = e.detail; - eventData.msgs.forEach((msgID: number) => { - let message = this.getMessage(msgID); - message.webpage = appWebPagesManager.getWebPage(eventData.id); // warning + eventData.msgs.forEach((msgID) => { + let message = this.getMessage(msgID) as Message.message; + (message.media as MessageMedia.messageMediaWebPage).webpage = appWebPagesManager.getWebPage(eventData.id); // warning $rootScope.$broadcast('message_edit', { peerID: this.getMessagePeer(message), id: message.id, @@ -626,6 +629,7 @@ export class AppMessagesManager { peer: appPeersManager.getInputPeerByID(peerID), id: appMessagesIDsManager.getMessageLocalID(messageID), message: text, + // @ts-ignore media: message.media, entities: this.getInputEntities(entities), no_webpage: noWebPage || undefined, @@ -2024,9 +2028,10 @@ export class AppMessagesManager { return Promise.all(promises); } - public getMessage(messageID: number) { + public getMessage(messageID: number)/* : Message */ { return this.messagesStorage[messageID] || { _: 'messageEmpty', + id: messageID, deleted: true, pFlags: {out: false, unread: false} }; @@ -2245,9 +2250,9 @@ export class AppMessagesManager { apiMessage.viaBotID = apiMessage.via_bot_id; } - const mediaContext = { - user_id: apiMessage.fromID, - date: apiMessage.date + const mediaContext: ReferenceContext = { + type: 'message', + messageID: mid }; if(apiMessage.media) { @@ -2638,7 +2643,8 @@ export class AppMessagesManager { let topMessage = dialog.top_message; const topPendingMessage = this.pendingTopMsgs[peerID]; if(topPendingMessage) { - if(!topMessage || this.getMessage(topPendingMessage).date > this.getMessage(topMessage).date) { + if(!topMessage + || (this.getMessage(topPendingMessage) as MyMessage).date > (this.getMessage(topMessage) as MyMessage).date) { dialog.top_message = topMessage = topPendingMessage; } } @@ -3764,11 +3770,21 @@ export class AppMessagesManager { for(let i = 0; i < update.messages.length; i++) { let messageID = appMessagesIDsManager.getFullMessageID(update.messages[i], channelID); - let message = this.messagesStorage[messageID]; + let message: MyMessage = this.messagesStorage[messageID]; if(message) { let peerID = this.getMessagePeer(message); let history = historiesUpdated[peerID] || (historiesUpdated[peerID] = {count: 0, unread: 0, msgs: {}}); + if((message as Message.message).media) { + // @ts-ignore + let c = message.media.webpage || message.media; + const smth = c.photo || c.document; + + if(smth.file_reference) { + referenceDatabase.deleteContext(smth.file_reference, {type: 'message', messageID}); + } + } + if(!message.pFlags.out && message.pFlags.unread) { history.unread++; } @@ -4228,29 +4244,34 @@ export class AppMessagesManager { }, { //timeout: APITIMEOUT, noErrorBox: true - }).then((historyResult: any) => { + }).then((historyResult) => { this.log('requestHistory result:', historyResult, maxID, limit, offset); + if(historyResult._ == 'messages.messagesNotModified') { + return historyResult; + } + appUsersManager.saveApiUsers(historyResult.users); appChatsManager.saveApiChats(historyResult.chats); this.saveMessages(historyResult.messages); if(isChannel) { - apiUpdatesManager.addChannelState(-peerID, historyResult.pts); + apiUpdatesManager.addChannelState(-peerID, (historyResult as MessagesMessages.messagesChannelMessages).pts); } let length = historyResult.messages.length; if(length && historyResult.messages[length - 1].deleted) { historyResult.messages.splice(length - 1, 1); length--; - historyResult.count--; + (historyResult as MessagesMessages.messagesMessagesSlice).count--; } // will load more history if last message is album grouped (because it can be not last item) const historyStorage = this.historiesStorage[peerID]; // historyResult.messages: desc sorted - if(length && historyResult.messages[length - 1].grouped_id && (historyStorage.history.length + historyResult.messages.length) < historyResult.count) { - return this.requestHistory(peerID, historyResult.messages[length - 1].mid, 10, 0).then((_historyResult: any) => { + if(length && (historyResult.messages[length - 1] as Message.message).grouped_id + && (historyStorage.history.length + historyResult.messages.length) < (historyResult as MessagesMessages.messagesMessagesSlice).count) { + return this.requestHistory(peerID, (historyResult.messages[length - 1] as Message.message).mid, 10, 0).then((_historyResult) => { return historyResult; }); } @@ -4314,59 +4335,62 @@ export class AppMessagesManager { return this.fetchSingleMessagesPromise; } - const mids = this.needSingleMessages.slice(); - this.needSingleMessages.length = 0; - - const splitted = appMessagesIDsManager.splitMessageIDsByChannels(mids); - let promises: Promise[] = []; - Object.keys(splitted.msgIDs).forEach((channelID: number | string) => { - channelID = +channelID; - - const msgIDs: InputMessage[] = splitted.msgIDs[channelID].map((msgID: number) => { - return { - _: 'inputMessageID', - id: msgID - }; - }); - - let promise: Promise; - if(channelID > 0) { - promise = apiManager.invokeApi('channels.getMessages', { - channel: appChatsManager.getChannelInput(channelID), - id: msgIDs - }); - } else { - promise = apiManager.invokeApi('messages.getMessages', { - id: msgIDs + return this.fetchSingleMessagesPromise = new Promise((resolve) => { + setTimeout(() => { + const mids = this.needSingleMessages.slice(); + this.needSingleMessages.length = 0; + + const splitted = appMessagesIDsManager.splitMessageIDsByChannels(mids); + let promises: Promise[] = []; + Object.keys(splitted.msgIDs).forEach((channelID: number | string) => { + channelID = +channelID; + + const msgIDs: InputMessage[] = splitted.msgIDs[channelID].map((msgID: number) => { + return { + _: 'inputMessageID', + id: msgID + }; + }); + + let promise: Promise; + if(channelID > 0) { + promise = apiManager.invokeApi('channels.getMessages', { + channel: appChatsManager.getChannelInput(channelID), + id: msgIDs + }); + } else { + promise = apiManager.invokeApi('messages.getMessages', { + id: msgIDs + }); + } + + promises.push(promise.then(getMessagesResult => { + if(getMessagesResult._ != 'messages.messagesNotModified') { + appUsersManager.saveApiUsers(getMessagesResult.users); + appChatsManager.saveApiChats(getMessagesResult.chats); + this.saveMessages(getMessagesResult.messages); + } + + $rootScope.$broadcast('messages_downloaded', splitted.mids[+channelID]); + })); }); - } - promises.push(promise.then(getMessagesResult => { - if(getMessagesResult._ != 'messages.messagesNotModified') { - appUsersManager.saveApiUsers(getMessagesResult.users); - appChatsManager.saveApiChats(getMessagesResult.chats); - this.saveMessages(getMessagesResult.messages); - } - - $rootScope.$broadcast('messages_downloaded', splitted.mids[+channelID]); - })); - }); - - this.fetchSingleMessagesPromise = Promise.all(promises).finally(() => { - this.fetchSingleMessagesTimeout = 0; - this.fetchSingleMessagesPromise = null; - if(this.needSingleMessages.length) this.fetchSingleMessages(); + Promise.all(promises).finally(() => { + this.fetchSingleMessagesPromise = null; + if(this.needSingleMessages.length) this.fetchSingleMessages(); + resolve(); + }); + }, 0); }); } - public wrapSingleMessage(msgID: number, overwrite = false) { + public wrapSingleMessage(msgID: number, overwrite = false): Promise { if(this.messagesStorage[msgID] && !overwrite) { $rootScope.$broadcast('messages_downloaded', [msgID]); + return Promise.resolve(); } else if(this.needSingleMessages.indexOf(msgID) == -1) { this.needSingleMessages.push(msgID); - if(this.fetchSingleMessagesTimeout == 0) { - this.fetchSingleMessagesTimeout = window.setTimeout(this.fetchSingleMessages.bind(this), 10); - } + return this.fetchSingleMessages(); } } diff --git a/src/lib/appManagers/appPhotosManager.ts b/src/lib/appManagers/appPhotosManager.ts index 106c86fb..986d0322 100644 --- a/src/lib/appManagers/appPhotosManager.ts +++ b/src/lib/appManagers/appPhotosManager.ts @@ -1,13 +1,17 @@ -import { calcImageInBox, isObject } from "../utils"; +import { calcImageInBox, isObject, safeReplaceArrayInObject } from "../utils"; import { bytesFromHex, getFileNameByLocation } from "../bin_utils"; import appDownloadManager from "./appDownloadManager"; import { isSafari } from "../../helpers/userAgent"; import { FileLocation, InputFileLocation, Photo, PhotoSize } from "../../layer"; import { MyDocument } from "./appDocsManager"; import { CancellablePromise } from "../../helpers/cancellablePromise"; +import referenceDatabase, { ReferenceContext } from "../mtproto/referenceDatabase"; export type MyPhoto = Photo.photo; +// TIMES = 2 DUE TO SIDEBAR AND CHAT +//let TEST_FILE_REFERENCE = "5440692274120994569", TEST_FILE_REFERENCE_TIMES = 2; + export class AppPhotosManager { private photos: { [id: string]: MyPhoto @@ -34,20 +38,28 @@ export class AppPhotosManager { this.windowH = document.body.scrollHeight; } - public savePhoto(photo: Photo, context?: any) { + public savePhoto(photo: Photo, context?: ReferenceContext) { if(photo._ == 'photoEmpty') return undefined; - if(this.photos[photo.id]) return Object.assign(this.photos[photo.id], photo); + /* if(photo.id == TEST_FILE_REFERENCE) { + console.warn('Testing FILE_REFERENCE_EXPIRED'); + const bytes = [2, 67, 175, 43, 190, 0, 0, 20, 62, 95, 111, 33, 45, 99, 220, 116, 218, 11, 167, 127, 213, 18, 127, 32, 243, 202, 117, 80, 30]; + //photo.file_reference = new Uint8Array(bytes); + photo.file_reference = bytes; + if(!--TEST_FILE_REFERENCE_TIMES) { + TEST_FILE_REFERENCE = ''; + } + } */ - /* if(context) { - Object.assign(photo, context); - } */ // warning - - if(!photo.id) { - console.warn('no apiPhoto.id', photo); - } else this.photos[photo.id] = photo; + const oldPhoto = this.photos[photo.id]; + safeReplaceArrayInObject('file_reference', oldPhoto, photo); + referenceDatabase.saveContext(photo.file_reference, context); + + if(oldPhoto) { + return Object.assign(oldPhoto, photo); + } - return photo; + return this.photos[photo.id] = photo; } public choosePhotoSize(photo: MyPhoto | MyDocument, width = 0, height = 0) { diff --git a/src/lib/appManagers/appStateManager.ts b/src/lib/appManagers/appStateManager.ts index eea64ed0..d9b2e323 100644 --- a/src/lib/appManagers/appStateManager.ts +++ b/src/lib/appManagers/appStateManager.ts @@ -8,8 +8,10 @@ import apiUpdatesManager from './apiUpdatesManager'; import { copy } from '../utils'; import { logger } from '../logger'; import type { AppStickersManager } from './appStickersManager'; +import { App } from '../mtproto/mtproto_config'; const REFRESH_EVERY = 24 * 60 * 60 * 1000; // 1 day +const STATE_VERSION = App.version; type State = Partial<{ dialogs: Dialog[], @@ -24,7 +26,8 @@ type State = Partial<{ recentEmoji: string[], topPeers: number[], recentSearch: number[], - stickerSets: AppStickersManager['stickerSets'] + stickerSets: AppStickersManager['stickerSets'], + version: typeof STATE_VERSION }>; const REFRESH_KEYS = ['dialogs', 'allDialogsLoaded', 'messages', 'contactsList', 'stateCreatedTime', @@ -45,19 +48,26 @@ export class AppStateManager { return this.loaded = new Promise((resolve) => { AppStorage.get('state').then((state) => { const time = Date.now(); - if((state?.stateCreatedTime ?? 0) + REFRESH_EVERY < time) { - this.log('will refresh state', state.stateCreatedTime, time); - REFRESH_KEYS.forEach(key => { - delete state[key]; - }); - //state = {}; + if(state) { + if(state?.version != STATE_VERSION) { + state = {}; + } else if((state?.stateCreatedTime ?? 0) + REFRESH_EVERY < time) { + this.log('will refresh state', state.stateCreatedTime, time); + REFRESH_KEYS.forEach(key => { + delete state[key]; + }); + //state = {}; + } } - + + // will not throw error because state can be `FALSE` const {dialogs, allDialogsLoaded, peers, messages, contactsList, maxSeenMsgID, updates, filters} = state; this.state = state || {}; this.state.peers = peers || {}; + this.state.version = STATE_VERSION; + // ??= doesn't compiles if(!this.state.hasOwnProperty('stateCreatedTime')) { this.state.stateCreatedTime = Date.now(); } @@ -100,13 +110,6 @@ export class AppStateManager { } */ appMessagesManager.saveMessages(messages); - - // FIX FILE_REFERENCE_EXPIRED KOSTIL'1999 - for(let message of messages) { - if(message.media) { - appMessagesManager.wrapSingleMessage(message.mid, true); - } - } } if(allDialogsLoaded) { diff --git a/src/lib/appManagers/appWebPagesManager.ts b/src/lib/appManagers/appWebPagesManager.ts index 089c5595..1a7a20db 100644 --- a/src/lib/appManagers/appWebPagesManager.ts +++ b/src/lib/appManagers/appWebPagesManager.ts @@ -2,6 +2,7 @@ import { $rootScope, safeReplaceObject } from "../utils"; import appPhotosManager from "./appPhotosManager"; import appDocsManager from "./appDocsManager"; import { RichTextProcessor } from "../richtextprocessor"; +import { ReferenceContext } from "../mtproto/referenceDatabase"; class AppWebPagesManager { webpages: any = {}; @@ -19,7 +20,7 @@ class AppWebPagesManager { }); } - public saveWebPage(apiWebPage: any, messageID?: number, mediaContext?: any) { + public saveWebPage(apiWebPage: any, messageID?: number, mediaContext?: ReferenceContext) { if(apiWebPage.photo && apiWebPage.photo._ === 'photo') { //appPhotosManager.savePhoto(apiWebPage.photo, mediaContext); apiWebPage.photo = appPhotosManager.savePhoto(apiWebPage.photo, mediaContext); diff --git a/src/lib/bin_utils.ts b/src/lib/bin_utils.ts index 2ea3e5ad..82d29e6a 100644 --- a/src/lib/bin_utils.ts +++ b/src/lib/bin_utils.ts @@ -5,6 +5,8 @@ * https://github.com/zhukov/webogram/blob/master/LICENSE */ +import {str2bigInt, divInt_, int2bigInt, bigInt2str, bigInt2bytes} from '../vendor/leemon'; + // @ts-ignore import {BigInteger, SecureRandom} from 'jsbn'; import type { InputFileLocation, FileLocation } from '../layer'; @@ -23,11 +25,6 @@ export function gzipUncompress(bytes: ArrayBuffer, toString?: boolean): string | } /// #endif -var _logTimer = Date.now(); -export function dT () { - return '[' + ((Date.now() - _logTimer) / 1000).toFixed(3) + ']' -} - export function isObject(object: any) { return typeof(object) === 'object' && object !== null; } @@ -294,24 +291,6 @@ export function bufferConcats(...args: any[]) { return tmp/* .buffer */; } -export function longToInts(sLong: string) { - var divRem = bigStringInt(sLong).divideAndRemainder(bigint(0x100000000)); - - return [divRem[0].intValue(), divRem[1].intValue()]; -} - -export function bytesFromWords(wordArray: {words: number[] | Uint8Array | Uint32Array, sigBytes: number}) { - var words = wordArray.words; - var sigBytes = wordArray.sigBytes; - var bytes = []; - - for(var i = 0; i < sigBytes; i++) { - bytes.push((words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff); - } - - return bytes; -} - export function bytesFromWordss(input: Uint32Array) { var o = []; for(var i = 0; i < input.length * 4; i++) { @@ -336,10 +315,6 @@ export function bytesToWordss(input: ArrayBuffer | Uint8Array) { return new Uint32Array(words); } -export function longToBytes(sLong: string) { - return bytesFromWords({words: longToInts(sLong), sigBytes: 8}).reverse(); -} - export function longFromInts(high: number, low: number) { return bigint(high).shiftLeft(32).add(bigint(low)).toString(10); } diff --git a/src/lib/config.ts b/src/lib/config.ts index 907014b3..9ebcc466 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -1,4 +1,3 @@ -import EventListenerBase from '../helpers/eventListenerBase'; import mediaSizes from '../helpers/mediaSizes'; /*! * Webogram v0.7.0 - messaging web application for MTProto diff --git a/src/lib/crypto/crypto_utils.ts b/src/lib/crypto/crypto_utils.ts index e3a309e0..61d8d41a 100644 --- a/src/lib/crypto/crypto_utils.ts +++ b/src/lib/crypto/crypto_utils.ts @@ -7,17 +7,32 @@ import pako from 'pako/dist/pako_inflate.min.js'; import {str2bigInt, bpe, equalsInt, greater, copy_, eGCD_, add_, rightShift_, sub_, copyInt_, isZero, - // @ts-ignore - divide_, one, bigInt2str, powMod} from 'leemon';//from 'leemon'; + divide_, one, bigInt2str, powMod, bigInt2bytes} from '../../vendor/leemon';//from 'leemon'; // @ts-ignore import {BigInteger} from 'jsbn'; -import { addPadding, bytesToHex, bytesFromHex, nextRandomInt, bytesFromBigInt, dT, bytesFromWords, bytesToWordss, bytesFromWordss } from '../bin_utils'; +import { addPadding, bytesToHex, bytesFromHex, nextRandomInt, bytesFromBigInt, bytesToWordss, bytesFromWordss } from '../bin_utils'; -export function bytesFromLeemonBigInt(bigInt: BigInteger) { - var str = bigInt2str(bigInt, 16); - return bytesFromHex(str); +export function longToBytes(sLong: string) { + /* let perf = performance.now(); + for(let i = 0; i < 1000000; ++i) { + bytesFromWords({words: longToInts(sLong), sigBytes: 8}).reverse(); + } + console.log('longToBytes JSBN', sLong, performance.now() - perf); + + //const bytes = bytesFromWords({words: longToInts(sLong), sigBytes: 8}).reverse(); + + perf = performance.now(); + for(let i = 0; i < 1000000; ++i) { + bigInt2bytes(str2bigInt(sLong, 10)); + } + console.log('longToBytes LEEMON', sLong, performance.now() - perf); */ + + const bytes = bigInt2bytes(str2bigInt(sLong, 10), false); + //console.log('longToBytes', bytes, b); + + return bytes; } export function sha1HashSync(bytes: number[] | ArrayBuffer | Uint8Array) { @@ -225,7 +240,7 @@ export function pqPrimeLeemon(what: any) { // console.log(dT(), 'done', bigInt2str(what, 10), bigInt2str(P, 10), bigInt2str(Q, 10)) - return [bytesFromLeemonBigInt(P), bytesFromLeemonBigInt(Q), it]; + return [bigInt2bytes(P), bigInt2bytes(Q), it]; } export function bytesModPow(x: any, y: any, m: any) { diff --git a/src/lib/crypto/srp.ts b/src/lib/crypto/srp.ts index 157bf398..ce99ea55 100644 --- a/src/lib/crypto/srp.ts +++ b/src/lib/crypto/srp.ts @@ -1,8 +1,7 @@ import { bufferConcats, bytesToHex, bytesFromHex, bufferConcat, bytesXor } from "../bin_utils"; import CryptoWorker from "../crypto/cryptoworker"; import {str2bigInt, isZero, - // @ts-ignore - bigInt2str, powMod, int2bigInt, mult, mod, sub, bitSize, negative, add, greater} from 'leemon'; + bigInt2str, powMod, int2bigInt, mult, mod, sub, bitSize, negative, add, greater} from '../../vendor/leemon'; import {logger, LogLevels} from '../logger'; import { AccountPassword, PasswordKdfAlgo } from "../../layer"; @@ -41,8 +40,8 @@ export async function computeSRP(password: string, state: AccountPassword) { let B = str2bigInt(bytesToHex(state.srp_B), 16); let g = int2bigInt(algo.g, 32, 256); - log('p', bigInt2str(p, 16)); - log('B', bigInt2str(B, 16)); + //log('p', bigInt2str(p, 16)); + //log('B', bigInt2str(B, 16)); /* if(B.compareTo(BigInteger.ZERO) < 0) { console.error('srp_B < 0') @@ -67,7 +66,7 @@ export async function computeSRP(password: string, state: AccountPassword) { let pw_hash = await makePasswordHash(password, new Uint8Array(algo.salt1), new Uint8Array(algo.salt2)); let x = str2bigInt(bytesToHex(new Uint8Array(pw_hash)), 16); - log('computed pw_hash:', pw_hash, x, bytesToHex(new Uint8Array(pw_hash))); + //log('computed pw_hash:', pw_hash, x, bytesToHex(new Uint8Array(pw_hash))); var padArray = function(arr: any[], len: number, fill = 0) { return Array(len).fill(fill).concat(arr).slice(-len); @@ -77,25 +76,25 @@ export async function computeSRP(password: string, state: AccountPassword) { let gForHash = padArray(bytesFromHex(bigInt2str(g, 16)), 256); // like uint8array let b_for_hash = padArray(bytesFromHex(bigInt2str(B, 16)), 256); - log(bytesToHex(pForHash)); + /* log(bytesToHex(pForHash)); log(bytesToHex(gForHash)); - log(bytesToHex(b_for_hash)); + log(bytesToHex(b_for_hash)); */ let g_x = powMod(g, x, p); - log('g_x', bigInt2str(g_x, 16)); + //log('g_x', bigInt2str(g_x, 16)); let k: any = await CryptoWorker.sha256Hash(bufferConcat(pForHash, gForHash)); k = str2bigInt(bytesToHex(new Uint8Array(k)), 16); - log('k', bigInt2str(k, 16)); + //log('k', bigInt2str(k, 16)); // kg_x = (k * g_x) % p let kg_x = mod(mult(k, g_x), p); // good - log('kg_x', bigInt2str(kg_x, 16)); + //log('kg_x', bigInt2str(kg_x, 16)); let is_good_mod_exp_first = (modexp: any, prime: any) => { let diff = sub(prime, modexp); @@ -128,10 +127,10 @@ export async function computeSRP(password: string, state: AccountPassword) { //console.log('ITERATION'); - log('g a p', bigInt2str(g, 16), bigInt2str(a, 16), bigInt2str(p, 16)); + //log('g a p', bigInt2str(g, 16), bigInt2str(a, 16), bigInt2str(p, 16)); const A = powMod(g, a, p); - log('A MODPOW', bigInt2str(A, 16)); + //log('A MODPOW', bigInt2str(A, 16)); if(is_good_mod_exp_first(A, p)) { const a_for_hash = bytesFromHex(bigInt2str(A, 16)); @@ -147,9 +146,9 @@ export async function computeSRP(password: string, state: AccountPassword) { let {a, a_for_hash, u} = await generate_and_check_random(); - log('a', bigInt2str(a, 16)); + /* log('a', bigInt2str(a, 16)); log('a_for_hash', bytesToHex(a_for_hash)); - log('u', bigInt2str(u, 16)); + log('u', bigInt2str(u, 16)); */ // g_b = (B - kg_x) % p /* log('B - kg_x', bigInt2str(sub(B, kg_x), 16)); @@ -158,26 +157,26 @@ export async function computeSRP(password: string, state: AccountPassword) { let g_b; if(!greater(B, kg_x)) { - log('negative'); + //log('negative'); g_b = add(B, p); } else g_b = B; g_b = mod(sub(g_b, kg_x), p); /* let g_b = sub(B, kg_x); if(negative(g_b)) g_b = add(g_b, p); */ - log('g_b', bigInt2str(g_b, 16)); + //log('g_b', bigInt2str(g_b, 16)); /* if(!is_good_mod_exp_first(g_b, p)) throw new Error('bad g_b'); */ let ux = mult(u, x); - log('u and x multiply', bigInt2str(u, 16), bigInt2str(x, 16), bigInt2str(ux, 16)); + //log('u and x multiply', bigInt2str(u, 16), bigInt2str(x, 16), bigInt2str(ux, 16)); let a_ux = add(a, ux); let S = powMod(g_b, a_ux, p); let K = await CryptoWorker.sha256Hash(padArray(bytesFromHex(bigInt2str(S, 16)), 256)); - log('K', bytesToHex(K), new Uint32Array(new Uint8Array(K).buffer)); + //log('K', bytesToHex(K), new Uint32Array(new Uint8Array(K).buffer)); let h1 = await CryptoWorker.sha256Hash(pForHash); let h2 = await CryptoWorker.sha256Hash(gForHash); @@ -201,7 +200,7 @@ export async function computeSRP(password: string, state: AccountPassword) { }; - log('out', bytesToHex(out.A), bytesToHex(out.M1)); + //log('out', bytesToHex(out.A), bytesToHex(out.M1)); return out; /* console.log(gForHash, pForHash, bForHash); */ diff --git a/src/lib/lottieLoader.ts b/src/lib/lottieLoader.ts index 3a3b0bec..fb50dadc 100644 --- a/src/lib/lottieLoader.ts +++ b/src/lib/lottieLoader.ts @@ -3,7 +3,7 @@ import animationIntersector from "../components/animationIntersector"; import apiManager from "./mtproto/mtprotoworker"; import EventListenerBase from "../helpers/eventListenerBase"; import mediaSizes from "../helpers/mediaSizes"; -import { isApple, isSafari } from "../helpers/userAgent"; +import { isAndroid, isApple, isAppleMobile, isSafari } from "../helpers/userAgent"; import RLottieWorker from 'worker-loader!./rlottie/rlottie.worker'; let convert = (value: number) => { @@ -93,7 +93,7 @@ export class RLottiePlayer extends EventListenerBase<{ // Skip ratio let skipRatio: number; if(options.skipRatio !== undefined) skipRatio = options.skipRatio; - else if(mediaSizes.isMobile && this.width < 100 && this.height < 100) { + else if((isAndroid || isAppleMobile) && this.width < 100 && this.height < 100) { skipRatio = 0.5; } @@ -106,15 +106,20 @@ export class RLottiePlayer extends EventListenerBase<{ if(options.needUpscale) { this.width = Math.round(this.width * pixelRatio); this.height = Math.round(this.height * pixelRatio); - } else if(pixelRatio > 1 && this.width > 100 && this.height > 100) { - if(isApple || !mediaSizes.isMobile) { - /* this.width = Math.round(this.width * (pixelRatio - 1)); - this.height = Math.round(this.height * (pixelRatio - 1)); */ - this.width = Math.round(this.width * pixelRatio); - this.height = Math.round(this.height * pixelRatio); - } else if(pixelRatio > 2.5) { - this.width = Math.round(this.width * (pixelRatio - 1.5)); - this.height = Math.round(this.height * (pixelRatio - 1.5)); + } else if(pixelRatio > 1) { + if(this.width > 100 && this.height > 100) { + if(isApple || !mediaSizes.isMobile) { + /* this.width = Math.round(this.width * (pixelRatio - 1)); + this.height = Math.round(this.height * (pixelRatio - 1)); */ + this.width = Math.round(this.width * pixelRatio); + this.height = Math.round(this.height * pixelRatio); + } else if(pixelRatio > 2.5) { + this.width = Math.round(this.width * (pixelRatio - 1.5)); + this.height = Math.round(this.height * (pixelRatio - 1.5)); + } + } else { + this.width = Math.round(this.width * Math.max(1.5, pixelRatio - 1.5)); + this.height = Math.round(this.height * Math.max(1.5, pixelRatio - 1.5)); } } } @@ -159,12 +164,14 @@ export class RLottiePlayer extends EventListenerBase<{ } public loadFromData(jsonString: string) { - this.sendQuery('loadFromData', jsonString, this.width, this.height); + this.sendQuery('loadFromData', jsonString, this.width, this.height/* , this.canvas.transferControlToOffscreen() */); } public play() { if(!this.paused) return; + //return; + //console.log('RLOTTIE PLAY' + this.reqId); this.paused = false; @@ -373,7 +380,9 @@ export class RLottiePlayer extends EventListenerBase<{ console.timeEnd('cache' + this.reqId); */ //console.log('cached'); - //return; + /* this.el.innerHTML = ''; + this.el.append(this.canvas); + return; */ this.requestFrame(0); this.setListenerResult('ready'); @@ -444,29 +453,29 @@ class QueryableWorker extends EventListenerBase { } public sendQuery(queryMethod: string, ...args: any[]) { - var args = Array.prototype.slice.call(arguments, 1); if(isSafari) { this.worker.postMessage({ 'queryMethod': queryMethod, 'queryMethodArguments': args }); } else { - var transfer = []; - for(var i = 0; i < args.length; i++) { - if(args[i] instanceof ArrayBuffer) { - transfer.push(args[i]); + //const transfer: (ArrayBuffer | OffscreenCanvas)[] = []; + const transfer: ArrayBuffer[] = []; + args.forEach(arg => { + if(arg instanceof ArrayBuffer) { + transfer.push(arg); } - if(args[i].buffer && args[i].buffer instanceof ArrayBuffer) { - transfer.push(args[i].buffer); + if(arg.buffer && arg.buffer instanceof ArrayBuffer) { + transfer.push(arg.buffer); } - } + }); //console.log('transfer', transfer); this.worker.postMessage({ 'queryMethod': queryMethod, 'queryMethodArguments': args - }, transfer); + }, transfer as PostMessageOptions); } } } diff --git a/src/lib/mediaPlayer.ts b/src/lib/mediaPlayer.ts index 9cf05ff2..0c56f67b 100644 --- a/src/lib/mediaPlayer.ts +++ b/src/lib/mediaPlayer.ts @@ -150,7 +150,7 @@ export class MediaProgressLine extends ProgressLine { clearTimeout(this.stopAndScrubTimeout); } - this.stopAndScrubTimeout = setTimeout(() => { + this.stopAndScrubTimeout = window.setTimeout(() => { !this.media.paused && this.media.pause(); this.stopAndScrubTimeout = 0; }, 150); @@ -412,7 +412,7 @@ export default class VideoPlayer { let showControlsTimeout = 0; const t = () => { - showControlsTimeout = setTimeout(() => { + showControlsTimeout = window.setTimeout(() => { showControlsTimeout = 0; player.classList.remove('show-controls'); }, 3e3); @@ -489,7 +489,7 @@ export default class VideoPlayer { video.addEventListener('play', () => { iconVolume.style.display = 'none'; - updateInterval = setInterval(() => { + updateInterval = window.setInterval(() => { //elapsed += 0.02; // Increase with timer interval if(video.currentTime != prevTime) { elapsed = video.currentTime; // Update if getCurrentTime was changed @@ -555,7 +555,7 @@ export default class VideoPlayer { let prevTime = 0; if(skin === 'circle') { - updateInterval = setInterval(() => { + updateInterval = window.setInterval(() => { if(video.currentTime != prevTime) { elapsed = video.currentTime; // Update if getCurrentTime was changed prevTime = video.currentTime; diff --git a/src/lib/mtproto/apiFileManager.ts b/src/lib/mtproto/apiFileManager.ts index 21de827a..170fbad0 100644 --- a/src/lib/mtproto/apiFileManager.ts +++ b/src/lib/mtproto/apiFileManager.ts @@ -232,6 +232,7 @@ export class ApiFileManager { let resolved = false; let cacheFileWriter: ReturnType; let errorHandler = (error: any) => { + delete this.cachedDownloadPromises[fileName]; deferred.reject(error); errorHandler = () => {}; diff --git a/src/lib/mtproto/authorizer.ts b/src/lib/mtproto/authorizer.ts index 4ea088d0..484124f1 100644 --- a/src/lib/mtproto/authorizer.ts +++ b/src/lib/mtproto/authorizer.ts @@ -10,6 +10,7 @@ import { BigInteger } from "jsbn"; import CryptoWorker from "../crypto/cryptoworker"; import { logger, LogLevels } from "../logger"; +//import { bigInt2str, greater, int2bigInt, one, powMod, str2bigInt, sub } from "../../vendor/leemon"; /* let fNewNonce: any = bytesFromHex('8761970c24cb2329b5b2459752c502f3057cb7e8dbab200e526e8767fdc73b3c').reverse(); let fNonce: any = bytesFromHex('b597720d11faa5914ef485c529cde414').reverse(); @@ -42,8 +43,8 @@ type AuthOptions = { b?: number[], g?: number, - gA?: number, - dhPrime?: number, + gA?: Uint8Array, + dhPrime?: Uint8Array, tmpAesKey?: Uint8Array, tmpAesIv?: Uint8Array, @@ -64,7 +65,7 @@ export class Authorizer { private log: ReturnType; constructor() { - this.log = logger(`AUTHORIZER`/* , LogLevels.error | LogLevels.log */); + this.log = logger(`AUTHORIZER`, LogLevels.error | LogLevels.log); } public mtpSendPlainRequest(dcID: number, requestArray: Uint8Array) { @@ -365,8 +366,8 @@ export class Authorizer { timeManager.applyServerTime(auth.serverTime, auth.localTime); } - public mtpVerifyDhParams(g: number, dhPrime: any, gA: any) { - this.log('Verifying DH params'); + public mtpVerifyDhParams(g: number, dhPrime: Uint8Array, gA: Uint8Array) { + this.log('Verifying DH params', g, dhPrime, gA); var dhPrimeHex = bytesToHex(dhPrime); if(g != 3 || dhPrimeHex !== 'c71caeb9c6b1c9048e6c522f70f13f73980d40238e3e21c14934d037563d930f48198a0aa7c14058229493d22530f4dbfa336f6e0ac925139543aed44cce7c3720fd51f69458705ac68cd4fe6b6b13abdc9746512969328454f18faf8c595f642477fe96bb2a941d5bcd1d4ac8cc49880708fa9b378e3c4f3a9060bee67cf9a4a4a695811051907e162753b56b0f6b410dba74d8a84b2a14b3144e0ef1284754fd17ed950d5965b4b9dd46582db1178d169c6bc465b0d6ff9ca3928fef5b9ae4e418fc15e83ebea0f87fa9ff5eed70050ded2849f47bf959d956850ce929851f0d8115f635b105ee2e4e15d04b2454bf6f4fadf034b10403119cd8e3b92fcc5b') { // The verified value is from https://core.telegram.org/mtproto/security_guidelines @@ -375,13 +376,20 @@ export class Authorizer { this.log('dhPrime cmp OK'); var gABigInt = new BigInteger(bytesToHex(gA), 16); + //const _gABigInt = str2bigInt(bytesToHex(gA), 16); var dhPrimeBigInt = new BigInteger(dhPrimeHex, 16); + //const _dhPrimeBigInt = str2bigInt(dhPrimeHex, 16); + //this.log('gABigInt.compareTo(BigInteger.ONE) <= 0', gABigInt.compareTo(BigInteger.ONE), BigInteger.ONE.compareTo(BigInteger.ONE), greater(_gABigInt, one)); if(gABigInt.compareTo(BigInteger.ONE) <= 0) { + //if(!greater(_gABigInt, one)) { throw new Error('[MT] DH params are not verified: gA <= 1'); } - + + /* this.log('gABigInt.compareTo(dhPrimeBigInt.subtract(BigInteger.ONE)) >= 0', gABigInt.compareTo(dhPrimeBigInt.subtract(BigInteger.ONE)), + greater(gABigInt, sub(_dhPrimeBigInt, one))); */ if(gABigInt.compareTo(dhPrimeBigInt.subtract(BigInteger.ONE)) >= 0) { + //if(greater(gABigInt, sub(_dhPrimeBigInt, one))) { throw new Error('[MT] DH params are not verified: gA >= dhPrime - 1'); } this.log('1 < gA < dhPrime-1 OK'); @@ -389,8 +397,13 @@ export class Authorizer { var two = new BigInteger(/* null */''); two.fromInt(2); + //const _two = int2bigInt(2, 10, 0); + //this.log('_two:', bigInt2str(_two, 16), two.toString(16)); var twoPow = two.pow(2048 - 64); + //const _twoPow = powMod(_two, int2bigInt(2048 - 64, 10, 0), null); + //this.log('twoPow:', twoPow.toString(16), bigInt2str(_twoPow, 16)); + // this.log('gABigInt.compareTo(twoPow) < 0'); if(gABigInt.compareTo(twoPow) < 0) { throw new Error('[MT] DH params are not verified: gA < 2^{2048-64}'); } diff --git a/src/lib/mtproto/networker.ts b/src/lib/mtproto/networker.ts index 9f6b2ffe..b48eb0d3 100644 --- a/src/lib/mtproto/networker.ts +++ b/src/lib/mtproto/networker.ts @@ -1,6 +1,6 @@ import {isObject} from '../bin_utils'; import {convertToUint8Array, - bufferConcat, nextRandomInt, bytesToHex, longToBytes, + bufferConcat, nextRandomInt, bytesToHex, bytesCmp, bigStringInt} from '../bin_utils'; import {TLDeserialization, TLSerialization} from './tl_utils'; import CryptoWorker from '../crypto/cryptoworker'; @@ -15,6 +15,7 @@ import HTTP from './transports/http'; import { logger, LogLevels } from '../logger'; import { Modes, App } from './mtproto_config'; import { InvokeApiOptions } from '../../types'; +import { longToBytes } from '../crypto/crypto_utils'; //console.error('networker included!', new Error().stack); @@ -948,7 +949,7 @@ class MTPNetworker { clearTimeout(this.nextReqTimeout); this.nextReqTimeout = 0; if(delay > 0) { - this.nextReqTimeout = setTimeout(this.performScheduledRequest.bind(this), delay || 0); + this.nextReqTimeout = self.setTimeout(this.performScheduledRequest.bind(this), delay || 0); } else { setTimeout(this.performScheduledRequest.bind(this), 0); } diff --git a/src/lib/mtproto/referenceDatabase.ts b/src/lib/mtproto/referenceDatabase.ts index cc080ef7..f72251b4 100644 --- a/src/lib/mtproto/referenceDatabase.ts +++ b/src/lib/mtproto/referenceDatabase.ts @@ -1,9 +1,71 @@ +import { Photo } from "../../layer"; +import { deepEqual } from "../utils"; + +export type ReferenceContext = ReferenceContext.referenceContextProfilePhoto | ReferenceContext.referenceContextMessage; +export namespace ReferenceContext { + export type referenceContextProfilePhoto = { + type: 'profilePhoto', + peerID: number + }; + + export type referenceContextMessage = { + type: 'message', + messageID: number + }; +} + +export type ReferenceBytes = Photo.photo['file_reference']; +//type ReferenceBytes = Uint8Array; + class ReferenceDatabase { + private contexts: Map> = new Map(); + //private references: Map = new Map(); + + public saveContext(reference: ReferenceBytes, context: ReferenceContext) { + const contexts = this.contexts.get(reference) ?? new Set(); + + for(const _context of contexts) { + if(deepEqual(_context, context)) { + return; + } + } + + contexts.add(context); + this.contexts.set(reference, contexts); + } + + public getContext(reference: ReferenceBytes): ReferenceContext { + const contexts = this.contexts.get(reference); + return contexts ? contexts.values().next().value : undefined; + } + + public deleteContext(reference: ReferenceBytes, context: ReferenceContext) { + const contexts = this.contexts.get(reference); + if(contexts) { + for(const _context of contexts) { + if(deepEqual(_context, context)) { + contexts.delete(_context); + if(!contexts.size) { + this.contexts.delete(reference); + } + return true; + } + } + } + + return false; + } + /* public replaceReference(oldReference: ReferenceBytes, newReference: ReferenceBytes) { + const contexts = this.contexts.get(oldReference); + if(contexts) { + this.contexts.delete(oldReference); + this.contexts.set(newReference, contexts); + } + } */ } const referenceDatabase = new ReferenceDatabase(); -// @ts-ignore if(process.env.NODE_ENV != 'production') { (window as any).referenceDatabase = referenceDatabase; } diff --git a/src/lib/polyfill.ts b/src/lib/polyfill.ts index 5a11121d..7bc6ed55 100644 --- a/src/lib/polyfill.ts +++ b/src/lib/polyfill.ts @@ -25,12 +25,13 @@ Uint8Array.prototype.concat = function(...args: Array(callback: (value: T, index?: number, array?: Array) => void) { @@ -62,8 +63,9 @@ declare global { hex: string; randomize: () => Uint8Array, concat: (...args: Array) => Uint8Array, - toString: () => string, - toJSON: () => number[], + //toString: () => string, + //toJSON: () => number[], + toJSON: () => {type: 'bytes', value: number[]}, } interface Array { diff --git a/src/lib/rlottie/rlottie.worker.ts b/src/lib/rlottie/rlottie.worker.ts index ad3f822b..6fc6c0e3 100644 --- a/src/lib/rlottie/rlottie.worker.ts +++ b/src/lib/rlottie/rlottie.worker.ts @@ -1,7 +1,7 @@ importScripts('rlottie-wasm.js'); //import Module, { allocate, intArrayFromString } from './rlottie-wasm'; -const _Module = Module as any; +const _Module = (self as any).Module as any; const DEFAULT_FPS = 60; @@ -11,13 +11,23 @@ export class RLottieItem { private frameCount = 0; private dead = false; + //private context: OffscreenCanvasRenderingContext2D; - constructor(private reqId: number, jsString: string, private width: number, private height: number, private fps: number) { + constructor(private reqId: number, jsString: string, private width: number, private height: number, private fps: number/* , private canvas: OffscreenCanvas */) { this.fps = Math.max(1, Math.min(60, fps || DEFAULT_FPS)); + //this.context = canvas.getContext('2d'); + this.init(jsString); reply('loaded', this.reqId, this.frameCount, this.fps); + + /* let frame = 0; + setInterval(() => { + if(frame >= this.frameCount) frame = 0; + let _frame = frame++; + this.render(_frame, null); + }, 1000 / this.fps); */ } private init(jsString: string) { @@ -56,6 +66,8 @@ export class RLottieItem { } else { clamped.set(data); } + + //this.context.putImageData(new ImageData(clamped, this.width, this.height), 0, 0); reply('frame', this.reqId, frameNo, clamped); } catch(e) { @@ -94,13 +106,13 @@ class RLottieWorker { const worker = new RLottieWorker(); -Module.onRuntimeInitialized = function() { +_Module.onRuntimeInitialized = function() { worker.init(); }; const items: {[reqId: string]: RLottieItem} = {}; const queryableFunctions = { - loadFromData: function(reqId: number, jsString: string, width: number, height: number) { + loadFromData: function(reqId: number, jsString: string, width: number, height: number/* , canvas: OffscreenCanvas */) { try { // ! WARNING, с этой проверкой не все стикеры работают, например - ДУРКА /* if(!/"tgs":\s*?1./.test(jsString)) { @@ -112,7 +124,7 @@ const queryableFunctions = { //console.log('Rendering sticker:', reqId, frameRate, 'now rendered:', Object.keys(items).length); - items[reqId] = new RLottieItem(reqId, jsString, width, height, frameRate); + items[reqId] = new RLottieItem(reqId, jsString, width, height, frameRate/* , canvas */); } catch(e) { console.error('Invalid file for sticker:', jsString); reply('error', reqId, e); diff --git a/src/lib/storage.ts b/src/lib/storage.ts index 39dc7f40..da1695a7 100644 --- a/src/lib/storage.ts +++ b/src/lib/storage.ts @@ -1,5 +1,6 @@ import { Modes } from './mtproto/mtproto_config'; import { notifySomeone, isWorker } from '../helpers/context'; +import { parse, stringify } from '../helpers/json'; class ConfigStorage { public keyPrefix = ''; @@ -16,24 +17,24 @@ class ConfigStorage { return this.keyPrefix; } - get(keys: any, callback: any) { + get(keys: string | string[], callback: any) { var single = false; if(!Array.isArray(keys)) { keys = Array.prototype.slice.call(arguments); callback = keys.pop(); single = keys.length == 1; } - var result = [], - value; + var result = []; var allFound = true; - var prefix = this.storageGetPrefix(), - i, key; + var prefix = this.storageGetPrefix(); - for(i = 0; i < keys.length; i++) { - key = keys[i] = prefix + keys[i]; - if(key.substr(0, 3) != 'xt_' && this.cache[key] !== undefined) { + for(let key of keys) { + key = prefix + key; + + if(this.cache.hasOwnProperty(key)) { result.push(this.cache[key]); } else if(this.useLs) { + let value: any; try { value = localStorage.getItem(key); } catch(e) { @@ -41,7 +42,7 @@ class ConfigStorage { } try { - value = (value === undefined || value === null) ? false : JSON.parse(value); + value = (value === undefined || value === null) ? false : parse(value); } catch(e) { value = false; } @@ -68,10 +69,7 @@ class ConfigStorage { value = obj[key]; key = prefix + key; this.cache[key] = value; - value = JSON.stringify(value, (key, value) => { - if(key == 'downloaded' || (key == 'url' && value.indexOf('blob:') === 0)) return undefined; - return value; - }); + value = stringify(value); if(this.useLs) { try { diff --git a/src/lib/utils.ts b/src/lib/utils.ts index e062dc81..22240b1e 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -343,6 +343,28 @@ export function safeReplaceObject(wasObject: any, newObject: any) { } } +/** + * Will be used for FILE_REFERENCE_EXPIRED + * @param key + * @param wasObject + * @param newObject + */ +export function safeReplaceArrayInObject(key: K, wasObject: any, newObject: any) { + if('byteLength' in newObject[key]) { // Uint8Array + newObject[key] = [...newObject[key]]; + } + + if(wasObject && wasObject[key] != newObject[key]) { + wasObject[key].length = newObject[key].length; + (newObject[key] as any[]).forEach((v, i) => { + wasObject[key][i] = v; + }) + + /* wasObject[key].set(newObject[key]); */ + newObject[key] = wasObject[key]; + } +} + export function numberWithCommas(x: number) { var parts = x.toString().split("."); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); diff --git a/src/scripts/generate_mtproto_types.js b/src/scripts/generate_mtproto_types.js index db1c1f7c..9c034967 100644 --- a/src/scripts/generate_mtproto_types.js +++ b/src/scripts/generate_mtproto_types.js @@ -1,6 +1,7 @@ // @ts-check -const schema = require('./in/schema.json'); -const additional = require('./in/schema_additional_params.json'); +const schema = require(__dirname + '/in/schema.json'); +const additional = require(__dirname + '/in/schema_additional_params.json'); +const replace = require(__dirname + '/in/schema_replace_types.json'); const mtproto = schema.API; @@ -10,6 +11,12 @@ for(const constructor of additional) { }); const realConstructor = mtproto.constructors.find(c => c.predicate == constructor.predicate); + /* constructor.params.forEach(param => { + const index = realConstructor.params.findIndex(_param => _param.predicate == param.predicate); + if(index !== -1) { + realConstructor.params.splice(index, 1); + } + }); */ realConstructor.params.splice(realConstructor.params.length, 0, ...constructor.params); } @@ -80,7 +87,7 @@ const processParamType = (type) => { return 'string'; case 'bytes': - return 'Uint8Array | number[]'; + return 'Uint8Array'; case 'string': return 'string'; @@ -105,6 +112,10 @@ const processParams = (params, object = {}, parseBooleanFlags = true) => { name += '?'; } + if(replace[name]) { + type = replace[name]; + } + const processed = processParamType(type); if(type.includes('?true') && parseBooleanFlags) { if(!object.pFlags) object.pFlags = {}; @@ -238,4 +249,5 @@ for(const method in methodsMap) { } out += `}\n\n`; -require('fs').writeFileSync('./out/layer.d.ts', out); \ No newline at end of file +const path = process.argv[2]; +require('fs').writeFileSync((path || __dirname + '/out/') + 'layer.d.ts', out); \ No newline at end of file diff --git a/src/scripts/in/schema_additional_params.json b/src/scripts/in/schema_additional_params.json index 18314a49..2fb16c88 100644 --- a/src/scripts/in/schema_additional_params.json +++ b/src/scripts/in/schema_additional_params.json @@ -54,4 +54,30 @@ {"name": "peerID", "type": "number"}, {"name": "folder_id", "type": "number"} ] +}, { + "predicate": "message", + "params": [ + {"name": "mid", "type": "number"}, + {"name": "deleted", "type": "boolean"}, + {"name": "peerID", "type": "number"}, + {"name": "fromID", "type": "number"}, + {"name": "grouped_id", "type": "string"}, + {"name": "canBeEdited", "type": "boolean"}, + {"name": "unread", "type": "true"} + ] +}, { + "predicate": "messageService", + "params": [ + {"name": "mid", "type": "number"}, + {"name": "deleted", "type": "boolean"}, + {"name": "peerID", "type": "number"}, + {"name": "fromID", "type": "number"}, + {"name": "canBeEdited", "type": "boolean"}, + {"name": "unread", "type": "true"} + ] +}, { + "predicate": "messageEmpty", + "params": [ + {"name": "deleted", "type": "boolean"} + ] }] \ No newline at end of file diff --git a/src/scripts/in/schema_replace_types.json b/src/scripts/in/schema_replace_types.json new file mode 100644 index 00000000..675c0746 --- /dev/null +++ b/src/scripts/in/schema_replace_types.json @@ -0,0 +1,3 @@ +{ + "file_reference": "Uint8Array | number[]" +} \ No newline at end of file diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss index e63f6ad8..b950e864 100644 --- a/src/scss/partials/_chat.scss +++ b/src/scss/partials/_chat.scss @@ -249,15 +249,24 @@ padding: 0 .5rem; flex: 0 0 auto; - @include respond-to(handhelds) { - padding-bottom: .5rem; - } - @include respond-to(not-handhelds) { padding-left: 1rem; padding-right: 1rem; padding-bottom: 21px; } + + @include respond-to(handhelds) { + padding-bottom: .5rem; + } + + @include respond-to(esg-bottom) { + padding-bottom: .5rem; + + .btn-circle { + height: 46px; + width: 46px; + } + } } /* @include respond-to(handhelds) { @@ -313,7 +322,7 @@ align-self: flex-end; z-index: 2; } - + #btn-send { color: #9e9e9e; @@ -551,11 +560,15 @@ position: relative; z-index: 3; - @include respond-to(handhelds) { min-height: 46px; padding: .5px .5rem; - } + } + + @include respond-to(esg-bottom) { + min-height: 46px; + padding: .5px .5rem; + } &:after { content: ''; @@ -697,7 +710,7 @@ } html.no-touch &:hover { - background-color: rgba(112, 117, 121, 0.08); + background-color: var(--color-gray-hover); } &-border { diff --git a/src/scss/partials/_chatBubble.scss b/src/scss/partials/_chatBubble.scss index 4f55897e..5ab09e44 100644 --- a/src/scss/partials/_chatBubble.scss +++ b/src/scss/partials/_chatBubble.scss @@ -1776,11 +1776,11 @@ poll-element { } } - &-line { + /* &-line { use { } - } + } */ } avatar-element { diff --git a/src/scss/partials/_chatlist.scss b/src/scss/partials/_chatlist.scss index 5b8ea57e..131d3fae 100644 --- a/src/scss/partials/_chatlist.scss +++ b/src/scss/partials/_chatlist.scss @@ -26,7 +26,7 @@ input { --border-width: 1px; - background-color: rgba(112, 117, 121, .08); + background-color: var(--color-gray-hover); height: 40px; border-radius: 22px; border: var(--border-width) solid transparent; @@ -42,7 +42,7 @@ &:focus { --border-width: 2px; - background-color: rgba(112, 117, 121, 0); + background-color: transparent; border: 2px solid $button-primary-background; & + .tgico { @@ -151,13 +151,19 @@ } html.no-touch &:hover { - background: rgba(112, 117, 121, .08); + background: var(--color-gray-hover); } } - li.active, li.menu-open { + li.menu-open { > .rp { - background: rgba(112, 117, 121, 0.08); + background: var(--color-gray-hover); + } + } + + @include respond-to(not-handhelds) { + li.active > .rp { + background: var(--color-gray-hover); } } diff --git a/src/scss/partials/_emojiDropdown.scss b/src/scss/partials/_emojiDropdown.scss index 9affe825..b4f683be 100644 --- a/src/scss/partials/_emojiDropdown.scss +++ b/src/scss/partials/_emojiDropdown.scss @@ -188,7 +188,7 @@ } html.no-touch &:hover { - background-color: rgba(112, 117, 121, .08); + background-color: var(--color-gray-hover); } } } @@ -226,7 +226,7 @@ > .grid-item { html.no-touch &:hover { border-radius: 12px; - background-color: rgba(112, 117, 121, .08); + background-color: var(--color-gray-hover); } /* &:nth-child(5n+5) { @@ -243,6 +243,7 @@ > img { animation: fadeIn .2s ease forwards; + object-fit: contain; } } } @@ -310,7 +311,7 @@ &.active { &:not(.tgico-recent) { - background-color: rgba(112, 117, 121, .08); + background-color: var(--color-gray-hover); } } diff --git a/src/scss/partials/_leftSidebar.scss b/src/scss/partials/_leftSidebar.scss index e81e4159..fb945998 100644 --- a/src/scss/partials/_leftSidebar.scss +++ b/src/scss/partials/_leftSidebar.scss @@ -371,7 +371,7 @@ } html.no-touch &:hover { - background: rgba(112, 117, 121, 0.08); + background: var(--color-gray-hover); cursor: pointer; } diff --git a/src/scss/partials/_rightSidebar.scss b/src/scss/partials/_rightSidebar.scss index 241854e5..6eb14c09 100644 --- a/src/scss/partials/_rightSidebar.scss +++ b/src/scss/partials/_rightSidebar.scss @@ -605,7 +605,7 @@ &:hover { border-radius: 12px; - background-color: rgba(112, 117, 121, 0.08); + background-color: var(--color-gray-hover); } img { diff --git a/src/scss/partials/_selector.scss b/src/scss/partials/_selector.scss index 7319a6b3..bc32499c 100644 --- a/src/scss/partials/_selector.scss +++ b/src/scss/partials/_selector.scss @@ -45,7 +45,7 @@ &-user { color: #000; - background-color: rgba(112, 117, 121, 0.08); + background-color: var(--color-gray-hover); font-size: 16px; padding: 0 17px 0px 0px; line-height: 31px; diff --git a/src/scss/partials/_slider.scss b/src/scss/partials/_slider.scss index 1de7bc1a..382dd442 100644 --- a/src/scss/partials/_slider.scss +++ b/src/scss/partials/_slider.scss @@ -38,7 +38,7 @@ $slider-time: .25s; transition: background-color .15s ease-in-out; &:hover { - background-color: rgba(112, 117, 121, .08); + background-color: var(--color-gray-hover); } } diff --git a/src/scss/partials/popups/_popup.scss b/src/scss/partials/popups/_popup.scss index 21f7c706..d050c3a3 100644 --- a/src/scss/partials/popups/_popup.scss +++ b/src/scss/partials/popups/_popup.scss @@ -102,7 +102,7 @@ overflow: hidden; html.no-touch &:hover { - background-color: rgba(112, 117, 121, 0.08); + background-color: var(--color-gray-hover); } & + button { diff --git a/src/scss/partials/popups/_stickers.scss b/src/scss/partials/popups/_stickers.scss index a35e6841..374715c7 100644 --- a/src/scss/partials/popups/_stickers.scss +++ b/src/scss/partials/popups/_stickers.scss @@ -68,7 +68,7 @@ &:hover { border-radius: 12px; - background-color: rgba(112, 117, 121, 0.08); + background-color: var(--color-gray-hover); } img { diff --git a/src/scss/style.scss b/src/scss/style.scss index c14a246a..116184b1 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -65,10 +65,15 @@ $floating-left-sidebar: 925px; @else if $media == esg-top { // topbar + chat input + margin bottom + height of ESG @media only screen and (min-height: 570px) and (min-width: $small-screen + 1) { @content; } } + + @else if $media == esg-bottom { + @media only screen and (max-height: 569px) { @content; } + } } :root { --color-gray: #c4c9cc; + --color-gray-hover: rgba(112, 117, 121, .08); --layer-transition: .2s ease-in-out; //--layer-transition: .3s cubic-bezier(.33, 1, .68, 1); //--layer-transition: none; @@ -315,7 +320,7 @@ input, textarea { } html.no-touch &:hover { - background-color: rgba(112, 117, 121, .08); + background-color: var(--color-gray-hover); } } @@ -798,7 +803,7 @@ hr { grid-template-columns: calc(26px + 2rem) 1fr 50px; html.no-touch &:hover { - background-color: rgba(112, 117, 121, .08); + background-color: var(--color-gray-hover); } } @@ -920,7 +925,7 @@ input:focus, button:focus { pointer-events: all !important; &:not(.btn-primary).menu-open { - background-color: rgba(112, 117, 121, 0.08); + background-color: var(--color-gray-hover); } } diff --git a/src/vendor/leemon.ts b/src/vendor/leemon.ts new file mode 100644 index 00000000..fc836502 --- /dev/null +++ b/src/vendor/leemon.ts @@ -0,0 +1,2136 @@ +'use strict' +//@flow + +/** * * * * * * * * * * + * Big Integer Library * + * Created 2000 * + * Leemon Baird * + * www.leemon.com * + * * * * * * * * * * * */ + +//////////////////////////////////////////////////////////////////////////////////////// +// These functions are designed to avoid frequent dynamic memory allocation in the inner loop. +// For most functions, if it needs a BigInt as a local variable it will actually use +// a global, and will only allocate to it only when it's not the right size. This ensures +// that when a function is called repeatedly with same-sized parameters, it only allocates +// memory on the first call. +// +// Note that for cryptographic purposes, the calls to Math.random() must +// be replaced with calls to a better pseudorandom number generator. +// +// In the following, "bigInt" means a bigInt with at least one leading zero element, +// and "integer" means a nonnegative integer less than radix. In some cases, integer +// can be negative. Negative bigInts are 2s complement. +// +// The following functions do not modify their inputs. +// Those returning a bigInt, string, or Array will dynamically allocate memory for that value. +// Those returning a boolean will return the integer 0 (false) or 1 (true). +// Those returning boolean or int will not allocate memory except possibly on the first +// time they're called with a given parameter size. +// +// bigInt add(x,y) //return (x+y) for bigInts x and y. +// bigInt addInt(x,n) //return (x+n) where x is a bigInt and n is an integer. +// string bigInt2str(x,base) //return a string form of bigInt x in a given base, with 2 <= base <= 95 +// int bitSize(x) //return how many bits long the bigInt x is, not counting leading zeros +// bigInt dup(x) //return a copy of bigInt x +// boolean equals(x,y) //is the bigInt x equal to the bigint y? +// boolean equalsInt(x,y) //is bigint x equal to integer y? +// bigInt expand(x,n) //return a copy of x with at least n elements, adding leading zeros if needed +// Array findPrimes(n) //return array of all primes less than integer n +// bigInt GCD(x,y) //return greatest common divisor of bigInts x and y (each with same number of elements). +// boolean greater(x,y) //is x>y? (x and y are nonnegative bigInts) +// boolean greaterShift(x,y,shift)//is (x <<(shift*bpe)) > y? +// bigInt int2bigInt(t,n,m) //return a bigInt equal to integer t, with at least n bits and m array elements +// bigInt inverseMod(x,n) //return (x**(-1) mod n) for bigInts x and n. If no inverse exists, it returns null +// int inverseModInt(x,n) //return x**(-1) mod n, for integers x and n. Return 0 if there is no inverse +// boolean isZero(x) //is the bigInt x equal to zero? +// boolean millerRabin(x,b) //does one round of Miller-Rabin base integer b say that bigInt x is possibly prime? (b is bigInt, 1=1). If s=1, then the most significant of those n bits is set to 1. +// bigInt randTruePrime(k) //return a new, random, k-bit, true prime bigInt using Maurer's algorithm. +// bigInt randProbPrime(k) //return a new, random, k-bit, probable prime bigInt (probability it's composite less than 2^-80). +// bigInt str2bigInt(s,b,n,m) //return a bigInt for number represented in string s in base b with at least n bits and m array elements +// bigInt sub(x,y) //return (x-y) for bigInts x and y. Negative answers will be 2s complement +// bigInt trim(x,k) //return a copy of x with exactly k leading zero elements +// +// +// The following functions each have a non-underscored version, which most users should call instead. +// These functions each write to a single parameter, and the caller is responsible for ensuring the array +// passed in is large enough to hold the result. +// +// void addInt_(x,n) //do x=x+n where x is a bigInt and n is an integer +// void add_(x,y) //do x=x+y for bigInts x and y +// void copy_(x,y) //do x=y on bigInts x and y +// void copyInt_(x,n) //do x=n on bigInt x and integer n +// void GCD_(x,y) //set x to the greatest common divisor of bigInts x and y, (y is destroyed). (This never overflows its array). +// boolean inverseMod_(x,n) //do x=x**(-1) mod n, for bigInts x and n. Returns 1 (0) if inverse does (doesn't) exist +// void mod_(x,n) //do x=x mod n for bigInts x and n. (This never overflows its array). +// void mult_(x,y) //do x=x*y for bigInts x and y. +// void multMod_(x,y,n) //do x=x*y mod n for bigInts x,y,n. +// void powMod_(x,y,n) //do x=x**y mod n, where x,y,n are bigInts (n is odd) and ** is exponentiation. 0**0=1. +// void randBigInt_(b,n,s) //do b = an n-bit random BigInt. if s=1, then nth bit (most significant bit) is set to 1. n>=1. +// void randTruePrime_(ans,k) //do ans = a random k-bit true random prime (not just probable prime) with 1 in the msb. +// void sub_(x,y) //do x=x-y for bigInts x and y. Negative answers will be 2s complement. +// +// The following functions do NOT have a non-underscored version. +// They each write a bigInt result to one or more parameters. The caller is responsible for +// ensuring the arrays passed in are large enough to hold the results. +// +// void addShift_(x,y,ys) //do x=x+(y<<(ys*bpe)) +// void carry_(x) //do carries and borrows so each element of the bigInt x fits in bpe bits. +// void divide_(x,y,q,r) //divide x by y giving quotient q and remainder r +// int divInt_(x,n) //do x=floor(x/n) for bigInt x and integer n, and return the remainder. (This never overflows its array). +// void eGCD_(x,y,d,a,b) //sets a,b,d to positive bigInts such that d = GCD_(x,y) = a*x-b*y +// void halve_(x) //do x=floor(|x|/2)*sgn(x) for bigInt x in 2's complement. (This never overflows its array). +// void leftShift_(x,n) //left shift bigInt x by n bits. n64 multiplier, but not with JavaScript's 32*32->32) +// - speeding up mont_(x,y,n,np) when x==y by doing a non-modular, non-Montgomery square +// followed by a Montgomery reduction. The intermediate answer will be twice as long as x, so that +// method would be slower. This is unfortunate because the code currently spends almost all of its time +// doing mont_(x,x,...), both for randTruePrime_() and powMod_(). A faster method for Montgomery squaring +// would have a large impact on the speed of randTruePrime_() and powMod_(). HAC has a couple of poorly-worded +// sentences that seem to imply it's faster to do a non-modular square followed by a single +// Montgomery reduction, but that's obviously wrong. +//////////////////////////////////////////////////////////////////////////////////////// + +export type Bool = 1 | 0 + +//globals +export var bpe = 0 //bits stored per array element +var mask = 0 //AND this with an array element to chop it down to bpe bits +var radix = mask + 1 //equals 2^bpe. A single 1 bit to the left of the last bit of mask. + +//the digits for converting to different bases +var digitsStr = + '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_=!@#$%^&*()[]{}|;:,.<>/?`~ \\\'"+-' + +//initialize the global variables + +//bpe=number of bits in the mantissa on this platform +for (bpe = 0; 1 << (bpe + 1) > 1 << bpe; bpe++); +bpe >>= 1 //bpe=number of bits in one element of the array representing the bigInt +mask = (1 << bpe) - 1 //AND the mask with an integer to get its bpe least significant bits +radix = mask + 1 //2^bpe. a single 1 bit to the left of the first bit of mask +export var one = int2bigInt(1, 1, 1) //constant used in powMod_() +export var zero = int2bigInt(0, 1, 1) + +//the following global variables are scratchpad memory to +//reduce dynamic memory allocation in the inner loop +var t: number[] | number = new Array(0) +var ss = t //used in mult_() +var s0 = t //used in multMod_(), squareMod_() +// var s1=t; //used in powMod_(), multMod_(), squareMod_() +// var s2=t; //used in powMod_(), multMod_() +var s3 = t //used in powMod_() +var s4 = t, + s5 = t //used in mod_() +var s6 = t //used in bigInt2str() +var s7 = t //used in powMod_() +var T = t //used in GCD_() +var sa = t //used in mont_() +var mr_x1 = t, + mr_r = t, + mr_a = t, //used in millerRabin() + eg_v = t, + eg_u = t, + eg_A = t, + eg_B = t, + eg_C = t, + eg_D = t, //used in eGCD_(), inverseMod_() + //, md_q1=t, md_q2=t, md_q3=t, md_r=t, md_r1=t, md_r2=t, md_tt=t, //used in mod_() + + primes = t, + pows = t, + s_i = t, + s_i2 = t, + s_R = t, + s_rm = t, + s_q = t, + s_n1 = t, + s_a = t, + s_r2 = t, + s_n = t, + s_b = t, + s_d = t, + s_x1 = t, + s_x2 = t, + s_aa = t, //used in randTruePrime_() + rpprb = t //used in randProbPrimeRounds() (which also uses "primes") + +//////////////////////////////////////////////////////////////////////////////////////// + +var k, buff + +/** + * return array of all primes less than integer n + * + * @param {number} n + * @returns {number[]} + */ +export function findPrimes(n: number): number[] { + var i, s, p, ans + s = new Array(n) + for (i = 0; i < n; i++) s[i] = 0 + s[0] = 2 + p = 0 //first p elements of s are primes, the rest are a sieve + for (; s[p] < n; ) { + //s[p] is the pth prime + for ( + i = s[p] * s[p]; + i < n; + i += s[p] //mark multiples of s[p] + ) + s[i] = 1 + p++ + s[p] = s[p - 1] + 1 + for (; s[p] < n && s[s[p]]; s[p]++); //find next prime (where s[p]==0) + } + ans = new Array(p) + for (i = 0; i < p; i++) ans[i] = s[i] + return ans +} + +/** + * does a single round of Miller-Rabin base b consider x to be a possible prime? + * + * x is a bigInt, and b is an integer, with b 0; j--); + for (z = 0, w = x[j]; w; w >>= 1, z++); + z += bpe * j + return z +} + +/** + * return a copy of x with at least n elements, adding leading zeros if needed + * + * @param {number[]} x + * @param {number} n + * @returns {number[]} + */ +export function expand(x: number[], n: number): number[] { + var ans = int2bigInt(0, (x.length > n ? x.length : n) * bpe, 0) + copy_(ans, x) + return ans +} + +/** + * return a k-bit true random prime using Maurer's algorithm. + * + * @export + * @param {number} k + * @returns {number[]} + */ +export function randTruePrime(k: number): number[] { + var ans = int2bigInt(0, k, 0) + randTruePrime_(ans, k) + return trim(ans, 1) +} + +/** + * return a k-bit random probable prime with probability of error < 2^-80 + * + * @export + * @param {number} k + * @returns {number[]} + */ +export function randProbPrime(k: number): number[] { + if (k >= 600) return randProbPrimeRounds(k, 2) //numbers from HAC table 4.3 + if (k >= 550) return randProbPrimeRounds(k, 4) + if (k >= 500) return randProbPrimeRounds(k, 5) + if (k >= 400) return randProbPrimeRounds(k, 6) + if (k >= 350) return randProbPrimeRounds(k, 7) + if (k >= 300) return randProbPrimeRounds(k, 9) + if (k >= 250) return randProbPrimeRounds(k, 12) //numbers from HAC table 4.4 + if (k >= 200) return randProbPrimeRounds(k, 15) + if (k >= 150) return randProbPrimeRounds(k, 18) + if (k >= 100) return randProbPrimeRounds(k, 27) + return randProbPrimeRounds(k, 40) //number from HAC remark 4.26 (only an estimate) +} + +/** + * return a k-bit probable random prime using n rounds of Miller Rabin + * (after trial division with small primes) + * + * @export + * @param {number} k + * @param {number} n + * @returns {number[]} + */ +export function randProbPrimeRounds(k: number, n: number): number[] { + var ans, i, divisible, B + B = 30000 //B is largest prime to use in trial division + ans = int2bigInt(0, k, 0) + + //optimization: try larger and smaller B to find the best limit. + + if (primes.length === 0) primes = findPrimes(30000) //check for divisibility by primes <=30000 + + if (rpprb.length !== ans.length) rpprb = dup(ans) + + for (;;) { + //keep trying random values for ans until one appears to be prime + //optimization: pick a random number times L=2*3*5*...*p, plus a + // random element of the list of all numbers in [0,L) not divisible by any prime up to p. + // This can reduce the amount of random number generation. + + randBigInt_(ans, k, 0) //ans = a random odd number to check + ans[0] |= 1 + divisible = 0 + + //check ans for divisibility by small primes up to B + for (i = 0; i < primes.length && primes[i] <= B; i++) + if (modInt(ans, primes[i]) === 0 && !equalsInt(ans, primes[i])) { + divisible = 1 + break + } + + //optimization: change millerRabin so the base can be bigger than the number being checked, then eliminate the while here. + + //do n rounds of Miller Rabin, with random bases less than ans + for (i = 0; i < n && !divisible; i++) { + randBigInt_(rpprb, k, 0) + while ( + !greater(ans, rpprb) //pick a random rpprb that's < ans + ) + randBigInt_(rpprb, k, 0) + if (!millerRabin(ans, rpprb)) divisible = 1 + } + + if (!divisible) return ans + } + /*:: + declare var never: empty + return never + */ +} + +/** + * return a new bigInt equal to (x mod n) for bigInts x and n. + * + * @param {number[]} x + * @param {number[]} n + * @returns {number[]} + */ +export function mod(x: number[], n: number[]): number[] { + var ans = dup(x) + mod_(ans, n) + return trim(ans, 1) +} + +/** + * return (x+n) where x is a bigInt and n is an integer. + * + * @export + * @param {number[]} x + * @param {number} n + * @returns {number[]} + */ +export function addInt(x: number[], n: number): number[] { + var ans = expand(x, x.length + 1) + addInt_(ans, n) + return trim(ans, 1) +} + +/** + * return x*y for bigInts x and y. This is faster when y y.length ? x.length + 1 : y.length + 1) + sub_(ans, y) + return trim(ans, 1) +} + +/** + * return (x+y) for bigInts x and y + * + * @export + * @param {number[]} x + * @param {number[]} y + * @returns {number[]} + */ +export function add(x: number[], y: number[]): number[] { + var ans = expand(x, x.length > y.length ? x.length + 1 : y.length + 1) + add_(ans, y) + return trim(ans, 1) +} + +/** + * return (x**(-1) mod n) for bigInts x and n. + * + * If no inverse exists, it returns null + * + * @param {number[]} x + * @param {number[]} n + * @returns {(number[] | null)} + */ +export function inverseMod(x: number[], n: number[]): number[] | null { + var ans = expand(x, n.length) + var s = inverseMod_(ans, n) + return s ? trim(ans, 1) : null +} + +/** + * return (x*y mod n) for bigInts x,y,n. + * + * For greater speed, let y= 2 + + if (s_i2.length != ans.length) { + s_i2 = dup(ans) + s_R = dup(ans) + s_n1 = dup(ans) + s_r2 = dup(ans) + s_d = dup(ans) + s_x1 = dup(ans) //TODO Seems like a bug in eslint, reports as unused + s_x2 = dup(ans) + s_b = dup(ans) + s_n = dup(ans) + s_i = dup(ans) + s_rm = dup(ans) + s_q = dup(ans) + s_a = dup(ans) + s_aa = dup(ans) + } + + if (k <= recLimit) { + //generate small random primes by trial division up to its square root + pm = (1 << ((k + 2) >> 1)) - 1 //pm is binary number with all ones, just over sqrt(2^k) + copyInt_(ans, 0) + for (dd = 1; dd; ) { + dd = 0 + ans[0] = 1 | (1 << (k - 1)) | Math.floor(Math.random() * (1 << k)) //random, k-bit, odd integer, with msb 1 + for (j = 1; j < primes.length && (primes[j] & pm) == primes[j]; j++) { + //trial division by all primes 3...sqrt(2^k) + if (0 == ans[0] % primes[j]) { + dd = 1 + break + } + } + } + carry_(ans) + return + } + + B = c * k * k //try small primes up to B (or all the primes[] array if the largest is less than B). + if (k > 2 * m) + //generate this k-bit number by first recursively generating a number that has between k/2 and k-m bits + for (r = 1; k - k * r <= m; ) r = pows[Math.floor(Math.random() * 512)] //r=Math.pow(2,Math.random()-1); + else r = 0.5 + + //simulation suggests the more complex algorithm using r=.333 is only slightly faster. + + recSize = Math.floor(r * k) + 1 + + randTruePrime_(s_q, recSize) + copyInt_(s_i2, 0) + s_i2[Math.floor((k - 2) / bpe)] |= 1 << ((k - 2) % bpe) //s_i2=2^(k-2) + divide_(s_i2, s_q, s_i, s_rm) //s_i=floor((2^(k-1))/(2q)) + + z = bitSize(s_i) + + for (;;) { + for (;;) { + //generate z-bit numbers until one falls in the range [0,s_i-1] + randBigInt_(s_R, z, 0) + if (greater(s_i, s_R)) break + } //now s_R is in the range [0,s_i-1] + addInt_(s_R, 1) //now s_R is in the range [1,s_i] + add_(s_R, s_i) //now s_R is in the range [s_i+1,2*s_i] + + copy_(s_n, s_q) + mult_(s_n, s_R) + multInt_(s_n, 2) + addInt_(s_n, 1) //s_n=2*s_R*s_q+1 + + copy_(s_r2, s_R) + multInt_(s_r2, 2) //s_r2=2*s_R + + //check s_n for divisibility by small primes up to B + for (divisible = 0, j = 0; j < primes.length && primes[j] < B; j++) + if (modInt(s_n, primes[j]) == 0 && !equalsInt(s_n, primes[j])) { + divisible = 1 + break + } + + if (!divisible) + if (!millerRabinInt(s_n, 2)) + //if it passes small primes check, then try a single Miller-Rabin base 2 + //this line represents 75% of the total runtime for randTruePrime_ + divisible = 1 + + if (!divisible) { + //if it passes that test, continue checking s_n + addInt_(s_n, -3) + for (j = s_n.length - 1; s_n[j] == 0 && j > 0; j--); //strip leading zeros + for (zz = 0, w = s_n[j]; w; w >>= 1, zz++); + zz += bpe * j //zz=number of bits in s_n, ignoring leading zeros + for (;;) { + //generate z-bit numbers until one falls in the range [0,s_n-1] + randBigInt_(s_a, zz, 0) + if (greater(s_n, s_a)) break + } //now s_a is in the range [0,s_n-1] + addInt_(s_n, 3) //now s_a is in the range [0,s_n-4] + addInt_(s_a, 2) //now s_a is in the range [2,s_n-2] + copy_(s_b, s_a) + copy_(s_n1, s_n) + addInt_(s_n1, -1) + powMod_(s_b, s_n1, s_n) //s_b=s_a^(s_n-1) modulo s_n + addInt_(s_b, -1) + if (isZero(s_b)) { + copy_(s_b, s_a) + powMod_(s_b, s_r2, s_n) + addInt_(s_b, -1) + copy_(s_aa, s_n) + copy_(s_d, s_b) + GCD_(s_d, s_n) //if s_b and s_n are relatively prime, then s_n is a prime + if (equalsInt(s_d, 1)) { + copy_(ans, s_aa) + return //if we've made it this far, then s_n is absolutely guaranteed to be prime + } + } + } + } +} + +/** + * Return an n-bit random BigInt (n>=1). If s=1, then the most significant of those n bits is set to 1. + * + * @export + * @param {number} n + * @param {number} s + * @returns {number[]} + */ +export function randBigInt(n: number, s: number): number[] { + var a, b + a = Math.floor((n - 1) / bpe) + 2 //# array elements to hold the BigInt with a leading 0 element + b = int2bigInt(0, 0, a) + randBigInt_(b, n, s) + return b +} + +/** + * Set b to an n-bit random BigInt. If s=1, then the most significant of those n bits is set to 1. + * + * Array b must be big enough to hold the result. Must have n>=1 + * + * @export + * @param {number[]} b + * @param {number} n + * @param {number} s + * @return {void} + */ +export function randBigInt_(b: number[], n: number, s: number): void { + var i, a + for (i = 0; i < b.length; i++) b[i] = 0 + a = Math.floor((n - 1) / bpe) + 1 //# array elements to hold the BigInt + for (i = 0; i < a; i++) { + b[i] = Math.floor(Math.random() * (1 << (bpe - 1))) + } + b[a - 1] &= (2 << ((n - 1) % bpe)) - 1 + if (s == 1) b[a - 1] |= 1 << ((n - 1) % bpe) +} + +/** + * Return the greatest common divisor of bigInts x and y (each with same number of elements). + * + * @export + * @param {number[]} x + * @param {number[]} y + * @returns {number[]} + */ +export function GCD(x: number[], y: number[]): number[] { + var xc, yc + xc = dup(x) + yc = dup(y) + GCD_(xc, yc) + return xc +} + +/** + * set x to the greatest common divisor of bigInts x and y (each with same number of elements). + * + * y is destroyed. + * + * @export + * @param {number[]} x + * @param {number[]} y + */ +export function GCD_(x: number[], y: number[]): void { + var i: number, xp: number, yp: number, A: number, B, C: number, D: number, q, sing + var qp + if (T.length !== x.length) T = dup(x) + + sing = 1 + while (sing) { + //while y has nonzero elements other than y[0] + sing = 0 + for ( + i = 1; + i < y.length; + i++ //check if y has nonzero elements other than 0 + ) + if (y[i]) { + sing = 1 + break + } + if (!sing) break //quit when y all zero elements except possibly y[0] + + for (i = x.length; !x[i] && i >= 0; i--); //find most significant element of x + xp = x[i] + yp = y[i] + A = 1 + B = 0 + C = 0 + D = 1 + while (yp + C && yp + D) { + q = Math.floor((xp + A) / (yp + C)) + qp = Math.floor((xp + B) / (yp + D)) + if (q != qp) break + t = A - q * C + A = C + C = t // do (A,B,xp, C,D,yp) = (C,D,yp, A,B,xp) - q*(0,0,0, C,D,yp) + t = B - q * D + B = D + D = t + t = xp - q * yp + xp = yp + yp = t + } + if (B) { + copy_(T, x) + linComb_(x, y, A, B) //x=A*x+B*y + linComb_(y, T, D, C) //y=D*y+C*T + } else { + mod_(x, y) + copy_(T, x) + copy_(x, y) + copy_(y, T) + } + } + if (y[0] === 0) return + t = modInt(x, y[0]) + copyInt_(x, y[0]) + y[0] = t + while (y[0]) { + x[0] %= y[0] + t = x[0] + x[0] = y[0] + y[0] = t + } +} + +/** + * do x=x**(-1) mod n, for bigInts x and n. + * + * If no inverse exists, it sets x to zero and returns 0, else it returns 1. + * The x array must be at least as large as the n array. + * + * @export + * @param {number[]} x + * @param {number[]} n + * @returns {(0 | 1)} + */ +export function inverseMod_(x: number[], n: number[]): Bool { + var k = 1 + 2 * Math.max(x.length, n.length) + + if (!(x[0] & 1) && !(n[0] & 1)) { + //if both inputs are even, then inverse doesn't exist + copyInt_(x, 0) + return 0 + } + + if (eg_u.length != k) { + eg_u = new Array(k) + eg_v = new Array(k) + eg_A = new Array(k) + eg_B = new Array(k) + eg_C = new Array(k) + eg_D = new Array(k) + } + + copy_(eg_u, x) + copy_(eg_v, n) + copyInt_(eg_A, 1) + copyInt_(eg_B, 0) + copyInt_(eg_C, 0) + copyInt_(eg_D, 1) + for (;;) { + while (!(eg_u[0] & 1)) { + //while eg_u is even + halve_(eg_u) + if (!(eg_A[0] & 1) && !(eg_B[0] & 1)) { + //if eg_A==eg_B==0 mod 2 + halve_(eg_A) + halve_(eg_B) + } else { + add_(eg_A, n) + halve_(eg_A) + sub_(eg_B, x) + halve_(eg_B) + } + } + + while (!(eg_v[0] & 1)) { + //while eg_v is even + halve_(eg_v) + if (!(eg_C[0] & 1) && !(eg_D[0] & 1)) { + //if eg_C==eg_D==0 mod 2 + halve_(eg_C) + halve_(eg_D) + } else { + add_(eg_C, n) + halve_(eg_C) + sub_(eg_D, x) + halve_(eg_D) + } + } + + if (!greater(eg_v, eg_u)) { + //eg_v <= eg_u + sub_(eg_u, eg_v) + sub_(eg_A, eg_C) + sub_(eg_B, eg_D) + } else { + //eg_v > eg_u + sub_(eg_v, eg_u) + sub_(eg_C, eg_A) + sub_(eg_D, eg_B) + } + + if (equalsInt(eg_u, 0)) { + while ( + negative(eg_C) //make sure answer is nonnegative + ) + add_(eg_C, n) + copy_(x, eg_C) + + if (!equalsInt(eg_v, 1)) { + //if GCD_(x,n)!=1, then there is no inverse + copyInt_(x, 0) + return 0 + } + return 1 + } + } + /*:: + declare var never: empty + return never + */ +} + +/** + * return x**(-1) mod n, for integers x and n. + * + * Return 0 if there is no inverse + * + * @param {number} x + * @param {number} n + * @returns {number} + */ +export function inverseModInt(x: number, n: number): number { + var a = 1, + b = 0, + t + for (;;) { + if (x === 1) return a + if (x === 0) return 0 + b -= a * Math.floor(n / x) + //$off + n %= x + + if (n === 1) return b //to avoid negatives, change this b to n-b, and each -= to += + if (n === 0) return 0 + a -= b * Math.floor(x / n) + //$off + x %= n + } + /*:: + declare var never: empty + return never + */ +} + +//this deprecated function is for backward compatibility only. +function inverseModInt_(x: number, n: number) { + return inverseModInt(x, n) +} + +/** + * Given positive bigInts x and y, change the bigints v, a, and b to positive bigInts such that: + * + * v = GCD_(x,y) = a*x-b*y + * + * The bigInts v, a, b, must have exactly as many elements as the larger of x and y. + * + * @export + * @param {number[]} x + * @param {number[]} y + * @param {number[]} v + * @param {number[]} a + * @param {number[]} b + * @return {void} + */ +export function eGCD_( + x: number[], + y: number[], + v: number[], + a: number[], + b: number[], +): void { + var g = 0 + var k = Math.max(x.length, y.length) + if (eg_u.length != k) { + eg_u = new Array(k) + eg_A = new Array(k) + eg_B = new Array(k) + eg_C = new Array(k) + eg_D = new Array(k) + } + while (!(x[0] & 1) && !(y[0] & 1)) { + //while x and y both even + halve_(x) + halve_(y) + g++ + } + copy_(eg_u, x) + copy_(v, y) + copyInt_(eg_A, 1) + copyInt_(eg_B, 0) + copyInt_(eg_C, 0) + copyInt_(eg_D, 1) + for (;;) { + while (!(eg_u[0] & 1)) { + //while u is even + halve_(eg_u) + if (!(eg_A[0] & 1) && !(eg_B[0] & 1)) { + //if A==B==0 mod 2 + halve_(eg_A) + halve_(eg_B) + } else { + add_(eg_A, y) + halve_(eg_A) + sub_(eg_B, x) + halve_(eg_B) + } + } + + while (!(v[0] & 1)) { + //while v is even + halve_(v) + if (!(eg_C[0] & 1) && !(eg_D[0] & 1)) { + //if C==D==0 mod 2 + halve_(eg_C) + halve_(eg_D) + } else { + add_(eg_C, y) + halve_(eg_C) + sub_(eg_D, x) + halve_(eg_D) + } + } + + if (!greater(v, eg_u)) { + //v<=u + sub_(eg_u, v) + sub_(eg_A, eg_C) + sub_(eg_B, eg_D) + } else { + //v>u + sub_(v, eg_u) + sub_(eg_C, eg_A) + sub_(eg_D, eg_B) + } + if (equalsInt(eg_u, 0)) { + while (negative(eg_C)) { + //make sure a (C) is nonnegative + add_(eg_C, y) + sub_(eg_D, x) + } + multInt_(eg_D, -1) ///make sure b (D) is nonnegative + copy_(a, eg_C) + copy_(b, eg_D) + leftShift_(v, g) + return + } + } +} + +/** + * is bigInt x negative? + * + * @param {number[]} x + * @returns {(1 | 0)} + */ +export function negative(x: number[]) { + //TODO Flow Bool type inference + return (x[x.length - 1] >> (bpe - 1)) & 1 +} + +/** + * is (x << (shift*bpe)) > y? + * + * x and y are nonnegative bigInts + * shift is a nonnegative integer + * + * @param {number[]} x + * @param {number[]} y + * @param {number} shift + * @returns {(1 | 0)} + */ +export function greaterShift(x: number[], y: number[], shift: number): Bool { + var i, + kx = x.length, + ky = y.length + k = kx + shift < ky ? kx + shift : ky + for (i = ky - 1 - shift; i < kx && i >= 0; i++) if (x[i] > 0) return 1 //if there are nonzeros in x to the left of the first column of y, then x is bigger + for (i = kx - 1 + shift; i < ky; i++) if (y[i] > 0) return 0 //if there are nonzeros in y to the left of the first column of x, then x is not bigger + for (i = k - 1; i >= shift; i--) + if (x[i - shift] > y[i]) return 1 + else if (x[i - shift] < y[i]) return 0 + return 0 +} + +/** + * is x > y? + * + * x and y both nonnegative + * + * @export + * @param {number[]} x + * @param {number[]} y + * @returns {(1 | 0)} + */ +export function greater(x: number[], y: number[]): Bool { + var i + var k = x.length < y.length ? x.length : y.length + + for (i = x.length; i < y.length; i++) if (y[i]) return 0 //y has more digits + + for (i = y.length; i < x.length; i++) if (x[i]) return 1 //x has more digits + + for (i = k - 1; i >= 0; i--) + if (x[i] > y[i]) return 1 + else if (x[i] < y[i]) return 0 + return 0 +} + +/** + * divide x by y giving quotient q and remainder r. + * + * q = floor(x/y) + * r = x mod y + * + * All 4 are bigints. + * + * * x must have at least one leading zero element. + * * y must be nonzero. + * * q and r must be arrays that are exactly the same length as x. (Or q can have more). + * * Must have x.length >= y.length >= 2. + * + * @export + * @param {number[]} x + * @param {number[]} y + * @param {number[]} q + * @param {number[]} r + * @return {void} + */ +export function divide_( + x: number[], + y: number[], + q: number[], + r: number[], +): void { + var kx, ky + var i, j, y1, y2, c, a, b + copy_(r, x) + for (ky = y.length; y[ky - 1] === 0; ky--); //ky is number of elements in y, not including leading zeros + + //normalize: ensure the most significant element of y has its highest bit set + b = y[ky - 1] + for (a = 0; b; a++) b >>= 1 + a = bpe - a //a is how many bits to shift so that the high order bit of y is leftmost in its array element + leftShift_(y, a) //multiply both by 1< ky; kx--); //kx is number of elements in normalized x, not including leading zeros + + copyInt_(q, 0) // q=0 + while (!greaterShift(y, r, kx - ky)) { + // while (leftShift_(y,kx-ky) <= r) { + subShift_(r, y, kx - ky) // r=r-leftShift_(y,kx-ky) + q[kx - ky]++ // q[kx-ky]++; + } // } + + for (i = kx - 1; i >= ky; i--) { + if (r[i] == y[ky - 1]) q[i - ky] = mask + else q[i - ky] = Math.floor((r[i] * radix + r[i - 1]) / y[ky - 1]) + + //The following for(;;) loop is equivalent to the commented while loop, + //except that the uncommented version avoids overflow. + //The commented loop comes from HAC, which assumes r[-1]==y[-1]==0 + // while (q[i-ky]*(y[ky-1]*radix+y[ky-2]) > r[i]*radix*radix+r[i-1]*radix+r[i-2]) + // q[i-ky]--; + for (;;) { + y2 = (ky > 1 ? y[ky - 2] : 0) * q[i - ky] + c = y2 >> bpe + y2 = y2 & mask + y1 = c + q[i - ky] * y[ky - 1] + c = y1 >> bpe + y1 = y1 & mask + + if ( + c == r[i] + ? y1 == r[i - 1] ? y2 > (i > 1 ? r[i - 2] : 0) : y1 > r[i - 1] + : c > r[i] + ) + q[i - ky]-- + else break + } + + linCombShift_(r, y, -q[i - ky], i - ky) //r=r-q[i-ky]*leftShift_(y,i-ky) + if (negative(r)) { + addShift_(r, y, i - ky) //r=r+leftShift_(y,i-ky) + q[i - ky]-- + } + } + + rightShift_(y, a) //undo the normalization step + rightShift_(r, a) //undo the normalization step +} + +/** + * do carries and borrows so each element of the bigInt x fits in bpe bits. + * + * @param {number[]} x + */ +export function carry_(x: number[]): void { + var i, k, c, b + k = x.length + c = 0 + for (i = 0; i < k; i++) { + c += x[i] + b = 0 + if (c < 0) { + b = -(c >> bpe) + c += b * radix + } + x[i] = c & mask + c = (c >> bpe) - b + } +} + +/** + * return x mod n for bigInt x and integer n. + * + * @export + * @param {number[]} x + * @param {number} n + * @returns {number} + */ +export function modInt(x: number[], n: number): number { + var i, + c = 0 + for (i = x.length - 1; i >= 0; i--) c = (c * radix + x[i]) % n + return c +} + +/** + * convert the integer t into a bigInt with at least the given number of bits. + * the returned array stores the bigInt in bpe-bit chunks, little endian (buff[0] is least significant word) + * Pad the array with leading zeros so that it has at least minSize elements. + * + * There will always be at least one leading 0 element. + * + * @export + * @param {number} t + * @param {number} bits + * @param {number} minSize + * @returns {number[]} + */ +export function int2bigInt(t: number, bits: number, minSize: number): number[] { + var i, k + k = Math.ceil(bits / bpe) + 1 + k = minSize > k ? minSize : k + var buff = new Array(k) + copyInt_(buff, t) + return buff +} + +/** + * return the bigInt given a string representation in a given base. + * Pad the array with leading zeros so that it has at least minSize elements. + * If base=-1, then it reads in a space-separated list of array elements in decimal. + * + * The array will always have at least one leading zero, unless base=-1. + * + * @export + * @param {string} s + * @param {number} base + * @param {number} [minSize] + * @returns {number[]} + */ +export function str2bigInt( + s: string, + base: number, + minSize?: number, +): number[] { + var d, i, x, y, kk + var k = s.length + if (base === -1) { + //comma-separated list of array elements in decimal + x = new Array(0) + for (;;) { + y = new Array(x.length + 1) + for (i = 0; i < x.length; i++) y[i + 1] = x[i] + y[0] = parseInt(s, 10) //TODO PERF Should we replace that with ~~ (not not)? https://jsperf.com/number-vs-parseint-vs-plus/7 + x = y + d = s.indexOf(',', 0) + if (d < 1) break + //$off + s = s.substring(d + 1) + if (s.length == 0) break + } + //$off + if (x.length < minSize) { + //$off + y = new Array(minSize) + copy_(y, x) + return y + } + return x + } + + x = int2bigInt(0, base * k, 0) + for (i = 0; i < k; i++) { + d = digitsStr.indexOf(s.substring(i, i + 1), 0) + if (base <= 36 && d >= 36) + //convert lowercase to uppercase if base<=36 + d -= 26 + if (d >= base || d < 0) { + //stop at first illegal character + break + } + multInt_(x, base) + addInt_(x, d) + } + + for (k = x.length; k > 0 && !x[k - 1]; k--); //strip off leading zeros + //$off + k = minSize > k + 1 ? minSize : k + 1 + //$off + y = new Array(k) + //$off + kk = k < x.length ? k : x.length + //$off + for (i = 0; i < kk; i++) y[i] = x[i] + //$off + for (; i < k; i++) y[i] = 0 + return y +} + +//return the bigInt given a string representation in a given base. +//Pad the array with leading zeros so that it has at least minSize elements. +//If base=-1, then it reads in a space-separated list of array elements in decimal. +//The array will always have at least one leading zero, unless base=-1. +// function str2bigInt(s,b,minSize) { +// var d, i, j, base, str, x, y, kk; +// if (typeof b === 'string') { +// base = b.length; +// str = b; +// } else { +// base = b; +// str = digitsStr; +// } +// var k=s.length; +// if (base==-1) { //comma-separated list of array elements in decimal +// x=new Array(0); +// for (;;) { +// y=new Array(x.length+1); +// for (i=0;i=36) { //convert lowercase to uppercase if base<=36 +// d-=26; +// } +// if (d>=base || d<0) { //ignore illegal characters +// continue; +// } +// multInt_(x,base); +// addInt_(x,d); +// } + +// for (k=x.length;k>0 && !x[k-1];k--); //strip off leading zeros +// k=minSize>k+1 ? minSize : k+1; +// y=new Array(k); +// kk=k y.length) { + for (; i < x.length; i++) if (x[i]) return 0 + } else { + for (; i < y.length; i++) if (y[i]) return 0 + } + return 1 +} + +/** + * is the bigInt x equal to zero? + * + * @export + * @param {number[]} x + * @returns {(1 | 0)} + */ +export function isZero(x: number[]): Bool { + var i + for (i = 0; i < x.length; i++) if (x[i]) return 0 + return 1 +} + +/** + * Convert a bigInt into a string in a given base, from base 2 up to base 95. + * + * Base -1 prints the contents of the array representing the number. + * + * @export + * @param {number[]} x + * @param {number} base + * @returns {string} + */ +export function bigInt2str(x: number[], base: number): string { + var i, + t, + s = '' + + if (s6.length !== x.length) s6 = dup(x) + else copy_(s6, x) + + if (base === -1) { + //return the list of array contents + for (i = x.length - 1; i > 0; i--) s += x[i] + ',' + s += x[0] + } else { + //return it in the given base + while (!isZero(s6)) { + t = divInt_(s6, base) //t=s6 % base; s6=floor(s6/base); + s = digitsStr.substring(t, t + 1) + s + } + } + if (s.length === 0) s = '0' + return s +} + +export function bigInt2bytes(x: number[], littleEndian = true) { + if(s6.length !== x.length) s6 = dup(x); + else copy_(s6, x); + + const out: number[] = []; + + //console.log('bigInt2bytes'); + while(!isZero(s6)) { + t = divInt_(s6, 256); //t=s6 % base; s6=floor(s6/base); + out.push(t); + //console.log('bigInt2bytes', t); + } + + if(littleEndian) { + out.reverse(); + } + + //console.log('bigInt2bytes', out); + + return out; +} + +/** + * Returns a duplicate of bigInt x + * + * @export + * @param {number[]} x + * @returns {number[]} + */ +export function dup(x: number[]): number[] { + var i + buff = Array(x.length) + copy_(buff, x) + return buff +} + +/** + * do x=y on bigInts x and y. + * + * x must be an array at least as big as y (not counting the leading zeros in y). + * + * @export + * @param {number[]} x + * @param {number[]} y + * @returns {void} + */ +export function copy_(x: number[], y: number[]): void { + var i + var k = x.length < y.length ? x.length : y.length + for (i = 0; i < k; i++) x[i] = y[i] + for (i = k; i < x.length; i++) x[i] = 0 +} + +/** + * do x=y on bigInt x and integer y. + * + * @export + * @param {number[]} x + * @param {number} n + * @returns {void} + */ +export function copyInt_(x: number[], n: number): void { + var i, c + var len = x.length //TODO .length in for loop have perfomance costs. Bench this + for (c = n, i = 0; i < len; i++) { + x[i] = c & mask + c >>= bpe + } +} + +/** + * do x=x+n where x is a bigInt and n is an integer. + * + * x must be large enough to hold the result. + * + * @export + * @param {number[]} x + * @param {number} n + * @returns {void} + */ +export function addInt_(x: number[], n: number): void { + var i, k, c, b + x[0] += n + k = x.length + c = 0 + for (i = 0; i < k; i++) { + c += x[i] + b = 0 + if (c < 0) { + b = -(c >> bpe) + c += b * radix + } + x[i] = c & mask + c = (c >> bpe) - b + if (!c) return //stop carrying as soon as the carry is zero + } +} + +/** + * right shift bigInt x by n bits. + * + * 0 <= n < bpe. + * + * @export + * @param {number[]} x + * @param {number} n + */ +export function rightShift_(x: number[], n: number): void { + var i + var k = Math.floor(n / bpe) + if (k) { + for ( + i = 0; + i < x.length - k; + i++ //right shift x by k elements + ) + x[i] = x[i + k] + for (; i < x.length; i++) x[i] = 0 + //$off + n %= bpe + } + for (i = 0; i < x.length - 1; i++) { + x[i] = mask & ((x[i + 1] << (bpe - n)) | (x[i] >> n)) + } + x[i] >>= n +} + +/** + * do x=floor(|x|/2)*sgn(x) for bigInt x in 2's complement + * + * @param {number[]} x + * @returns {void} + */ +export function halve_(x: number[]): void { + var i + for (i = 0; i < x.length - 1; i++) { + x[i] = mask & ((x[i + 1] << (bpe - 1)) | (x[i] >> 1)) + } + x[i] = (x[i] >> 1) | (x[i] & (radix >> 1)) //most significant bit stays the same +} + +/** + * left shift bigInt x by n bits + * + * @export + * @param {number[]} x + * @param {number} n + * @returns {void} + */ +export function leftShift_(x: number[], n: number): void { + var i + var k = Math.floor(n / bpe) + if (k) { + for ( + i = x.length; + i >= k; + i-- //left shift x by k elements + ) + x[i] = x[i - k] + for (; i >= 0; i--) x[i] = 0 + //$off + n %= bpe + } + if (!n) return + for (i = x.length - 1; i > 0; i--) { + x[i] = mask & ((x[i] << n) | (x[i - 1] >> (bpe - n))) + } + x[i] = mask & (x[i] << n) +} + +/** + * do x=x*n where x is a bigInt and n is an integer. + * + * x must be large enough to hold the result. + * + * @param {number[]} x + * @param {number} n + * @returns {void} + */ +export function multInt_(x: number[], n: number): void { + var i, k, c, b + if (!n) return + k = x.length + c = 0 + for (i = 0; i < k; i++) { + c += x[i] * n + b = 0 + if (c < 0) { + b = -(c >> bpe) + c += b * radix + } + x[i] = c & mask + c = (c >> bpe) - b + } +} + +/** + * do x=floor(x/n) for bigInt x and integer n, and return the remainder + * + * @param {number[]} x + * @param {number} n + * @returns {number} remainder + */ +export function divInt_(x: number[], n: number): number { + var i, + r = 0, + s + for (i = x.length - 1; i >= 0; i--) { + s = r * radix + x[i] + x[i] = Math.floor(s / n) + r = s % n + } + return r +} + +/** + * do the linear combination x=a*x+b*y for bigInts x and y, and integers a and b. + * + * x must be large enough to hold the answer. + * + * @param {number[]} x + * @param {number[]} y + * @param {number} a + * @param {number} b + * @returns {void} + */ +export function linComb_(x: number[], y: number[], a: number, b: number): void { + var i, c, k, kk + k = x.length < y.length ? x.length : y.length + kk = x.length + for (c = 0, i = 0; i < k; i++) { + c += a * x[i] + b * y[i] + x[i] = c & mask + c >>= bpe + } + for (i = k; i < kk; i++) { + c += a * x[i] + x[i] = c & mask + c >>= bpe + } +} + +/** + * do the linear combination x=a*x+b*(y<<(ys*bpe)) for bigInts x and y, and integers a, b and ys. + * + * x must be large enough to hold the answer. + * + * @param {number[]} x + * @param {number[]} y + * @param {number} b + * @param {number} ys + * @returns {void} + */ +export function linCombShift_( + x: number[], + y: number[], + b: number, + ys: number, +): void { + var i, c, k, kk + k = x.length < ys + y.length ? x.length : ys + y.length + kk = x.length + for (c = 0, i = ys; i < k; i++) { + c += x[i] + b * y[i - ys] + x[i] = c & mask + c >>= bpe + } + for (i = k; c && i < kk; i++) { + c += x[i] + x[i] = c & mask + c >>= bpe + } +} + +/** + * do x=x+(y<<(ys*bpe)) for bigInts x and y, and integer ys. + * + * x must be large enough to hold the answer. + * + * @export + * @param {number[]} x + * @param {number[]} y + * @param {number} ys + * @return {void} + */ +export function addShift_(x: number[], y: number[], ys: number): void { + var i, c, k, kk + k = x.length < ys + y.length ? x.length : ys + y.length + kk = x.length + for (c = 0, i = ys; i < k; i++) { + c += x[i] + y[i - ys] + x[i] = c & mask + c >>= bpe + } + for (i = k; c && i < kk; i++) { + c += x[i] + x[i] = c & mask + c >>= bpe + } +} + +/** + * do x=x-(y<<(ys*bpe)) for bigInts x and y, and integer ys + * + * x must be large enough to hold the answer + * + * @param {number[]} x + * @param {number[]} y + * @param {number} ys + * @return {void} + */ +export function subShift_(x: number[], y: number[], ys: number): void { + var i, c, k, kk + k = x.length < ys + y.length ? x.length : ys + y.length + kk = x.length + for (c = 0, i = ys; i < k; i++) { + c += x[i] - y[i - ys] + x[i] = c & mask + c >>= bpe + } + for (i = k; c && i < kk; i++) { + c += x[i] + x[i] = c & mask + c >>= bpe + } +} + +/** + * do x=x-y for bigInts x and y + * + * x must be large enough to hold the answer + * + * negative answers will be 2s complement + * + * @export + * @param {number[]} x + * @param {number[]} y + * @return {void} + */ +export function sub_(x: number[], y: number[]): void { + var i, c, k, kk + k = x.length < y.length ? x.length : y.length + for (c = 0, i = 0; i < k; i++) { + c += x[i] - y[i] + x[i] = c & mask + c >>= bpe + } + for (i = k; c && i < x.length; i++) { + c += x[i] + x[i] = c & mask + c >>= bpe + } +} + +/** + * do x=x+y for bigInts x and y + * + * x must be large enough to hold the answer + * + * @export + * @param {number[]} x + * @param {number[]} y + * @return {void} + */ +export function add_(x: number[], y: number[]): void { + var i, c, k, kk + k = x.length < y.length ? x.length : y.length + for (c = 0, i = 0; i < k; i++) { + c += x[i] + y[i] + x[i] = c & mask + c >>= bpe + } + for (i = k; c && i < x.length; i++) { + c += x[i] + x[i] = c & mask + c >>= bpe + } +} + +/** + * do x=x*y for bigInts x and y. + * + * This is faster when y 0 && !x[kx - 1]; kx--); //ignore leading zeros in x + k = kx > n.length ? 2 * kx : 2 * n.length //k=# elements in the product, which is twice the elements in the larger of x and n + if (s0.length != k) s0 = new Array(k) + copyInt_(s0, 0) + for (i = 0; i < kx; i++) { + c = s0[2 * i] + x[i] * x[i] + s0[2 * i] = c & mask + c >>= bpe + for (j = i + 1; j < kx; j++) { + c = s0[i + j] + 2 * x[i] * x[j] + c + s0[i + j] = c & mask + c >>= bpe + } + s0[i + kx] = c + } + mod_(s0, n) + copy_(x, s0) +} + +/** + * return x with exactly k leading zero elements + * + * @export + * @param {number[]} x + * @param {number} k + * @returns {number[]} + */ +export function trim(x: number[], k: number): number[] { + var i, y + for (i = x.length; i > 0 && !x[i - 1]; i--); + y = new Array(i + k) + copy_(y, x) + return y +} + +/** + * do `x=x**y mod n`, where x,y,n are bigInts and `**` is exponentiation. `0**0=1`. + * + * this is faster when n is odd. + * + * x usually needs to have as many elements as n. + * + * @param {number[]} x + * @param {number[]} y + * @param {number[]} n + * @return {void} + */ +export function powMod_(x: number[], y: number[], n: number[]): void { + var k1, k2, kn, np + if (s7.length != n.length) s7 = dup(n) + + //for even modulus, use a simple square-and-multiply algorithm, + //rather than using the more complex Montgomery algorithm. + if ((n[0] & 1) == 0) { + copy_(s7, x) + copyInt_(x, 1) + while (!equalsInt(y, 0)) { + if (y[0] & 1) multMod_(x, s7, n) + divInt_(y, 2) + squareMod_(s7, n) + } + return + } + + //calculate np from n for the Montgomery multiplications + copyInt_(s7, 0) + for (kn = n.length; kn > 0 && !n[kn - 1]; kn--); + np = radix - inverseModInt(modInt(n, radix), radix) + s7[kn] = 1 + multMod_(x, s7, n) // x = x * 2**(kn*bp) mod n + + if (s3.length != x.length) s3 = dup(x) + else copy_(s3, x) + //$off + // @ts-ignore + for (k1 = y.length - 1; (k1 > 0) & !y[k1]; k1--); //k1=first nonzero element of y + if (y[k1] == 0) { + //anything to the 0th power is 1 + copyInt_(x, 1) + return + } + for (k2 = 1 << (bpe - 1); k2 && !(y[k1] & k2); k2 >>= 1); //k2=position of first 1 bit in y[k1] + for (;;) { + if (!(k2 >>= 1)) { + //look at next bit of y + k1-- + if (k1 < 0) { + mont_(x, one, n, np) + return + } + k2 = 1 << (bpe - 1) + } + mont_(x, x, n, np) + + if (k2 & y[k1]) + //if next bit is a 1 + mont_(x, s3, n, np) + } +} + +/** + * do x=x*y*Ri mod n for bigInts x,y,n, + * where Ri = 2**(-kn*bpe) mod n, and kn is the + * number of elements in the n array, not + * counting leading zeros. + * + * x array must have at least as many elemnts as the n array + * It's OK if x and y are the same variable. + * + * must have: + * * x,y < n + * * n is odd + * * np = -(n^(-1)) mod radix + * + * @export + * @param {number[]} x + * @param {number[]} y + * @param {number[]} n + * @param {number} np + * @return {void} + */ +export function mont_(x: number[], y: number[], n: number[], np: number): void { + var i, j, c, ui, t, ks + var kn = n.length + var ky = y.length + + if (sa.length != kn) sa = new Array(kn) + + copyInt_(sa, 0) + + for (; kn > 0 && n[kn - 1] == 0; kn--); //ignore leading zeros of n + for (; ky > 0 && y[ky - 1] == 0; ky--); //ignore leading zeros of y + ks = sa.length - 1 //sa will never have more than this many nonzero elements. + + //the following loop consumes 95% of the runtime for randTruePrime_() and powMod_() for large numbers + for (i = 0; i < kn; i++) { + t = sa[0] + x[i] * y[0] + ui = ((t & mask) * np) & mask //the inner "& mask" was needed on Safari (but not MSIE) at one time + c = (t + ui * n[0]) >> bpe + t = x[i] + + //do sa=(sa+x[i]*y+ui*n)/b where b=2**bpe. Loop is unrolled 5-fold for speed + j = 1 + for (; j < ky - 4; ) { + c += sa[j] + ui * n[j] + t * y[j] + sa[j - 1] = c & mask + c >>= bpe + j++ + c += sa[j] + ui * n[j] + t * y[j] + sa[j - 1] = c & mask + c >>= bpe + j++ + c += sa[j] + ui * n[j] + t * y[j] + sa[j - 1] = c & mask + c >>= bpe + j++ + c += sa[j] + ui * n[j] + t * y[j] + sa[j - 1] = c & mask + c >>= bpe + j++ + c += sa[j] + ui * n[j] + t * y[j] + sa[j - 1] = c & mask + c >>= bpe + j++ + } + for (; j < ky; ) { + c += sa[j] + ui * n[j] + t * y[j] + sa[j - 1] = c & mask + c >>= bpe + j++ + } + for (; j < kn - 4; ) { + c += sa[j] + ui * n[j] + sa[j - 1] = c & mask + c >>= bpe + j++ + c += sa[j] + ui * n[j] + sa[j - 1] = c & mask + c >>= bpe + j++ + c += sa[j] + ui * n[j] + sa[j - 1] = c & mask + c >>= bpe + j++ + c += sa[j] + ui * n[j] + sa[j - 1] = c & mask + c >>= bpe + j++ + c += sa[j] + ui * n[j] + sa[j - 1] = c & mask + c >>= bpe + j++ + } + for (; j < kn; ) { + c += sa[j] + ui * n[j] + sa[j - 1] = c & mask + c >>= bpe + j++ + } + for (; j < ks; ) { + c += sa[j] + sa[j - 1] = c & mask + c >>= bpe + j++ + } + sa[j - 1] = c & mask + } + + if (!greater(n, sa)) sub_(sa, n) + copy_(x, sa) +} \ No newline at end of file