From be036138c3055a7a6472380e730a826e66fffee6 Mon Sep 17 00:00:00 2001 From: zubiden <19638254+zubiden@users.noreply.github.com> Date: Sun, 22 Feb 2026 23:43:28 +0100 Subject: [PATCH] Change pako to fflate (#6699) --- README.md | 2 +- package-lock.json | 15 +++- package.json | 2 +- public/compatTest.js | 4 +- src/@types/global.d.ts | 4 - src/lib/gramjs/extensions/MessagePacker.ts | 10 +-- src/lib/gramjs/network/MTProtoSender.ts | 6 +- src/lib/gramjs/network/MTProtoState.ts | 8 +- src/lib/gramjs/tl/core/GZIPPacked.ts | 86 ++++++++++------------ src/lib/rlottie/rlottie.worker.ts | 11 ++- 10 files changed, 73 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index cf843774a..11d428d13 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ await invoke(new GramJs.help.GetAppConfig()) ### Dependencies * [GramJS](https://github.com/gram-js/gramjs) ([MIT License](https://github.com/gram-js/gramjs/blob/master/LICENSE)) -* [pako](https://github.com/nodeca/pako) ([MIT License](https://github.com/nodeca/pako/blob/master/LICENSE)) +* [fflate](https://github.com/101arrowz/fflate) ([MIT License](https://github.com/101arrowz/fflate/blob/master/LICENSE)) * [cryptography](https://github.com/spalt08/cryptography) ([Apache License 2.0](https://github.com/spalt08/cryptography/blob/master/LICENSE)) * [emoji-data](https://github.com/iamcal/emoji-data) ([MIT License](https://github.com/iamcal/emoji-data/blob/master/LICENSE)) * [twemoji-parser](https://github.com/twitter/twemoji-parser) ([MIT License](https://github.com/twitter/twemoji-parser/blob/master/LICENSE.md)) diff --git a/package-lock.json b/package-lock.json index 0e4250226..6676b56f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,12 +18,12 @@ "@tauri-apps/plugin-updater": "^2.9.0", "async-mutex": "^0.5.0", "emoji-data-ios": "git+https://github.com/korenskoy/emoji-data-ios#443f1c9", + "fflate": "^0.8.2", "idb-keyval": "^6.2.2", "lowlight": "^3.3.0", "music-metadata": "^11.11.1", "opus-recorder": "github:Ajaxy/opus-recorder#116830a", "os-browserify": "^0.3.0", - "pako": "^2.1.0", "path-browserify": "^1.0.1", "qr-code-styling": "^1.9.2" }, @@ -3213,9 +3213,9 @@ } }, "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", + "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11753,6 +11753,12 @@ } } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -16264,6 +16270,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "dev": true, "license": "(MIT AND Zlib)" }, "node_modules/param-case": { diff --git a/package.json b/package.json index ba6fdb5fd..b2079a989 100644 --- a/package.json +++ b/package.json @@ -132,12 +132,12 @@ "@tauri-apps/plugin-updater": "^2.9.0", "async-mutex": "^0.5.0", "emoji-data-ios": "git+https://github.com/korenskoy/emoji-data-ios#443f1c9", + "fflate": "^0.8.2", "idb-keyval": "^6.2.2", "lowlight": "^3.3.0", "music-metadata": "^11.11.1", "opus-recorder": "github:Ajaxy/opus-recorder#116830a", "os-browserify": "^0.3.0", - "pako": "^2.1.0", "path-browserify": "^1.0.1", "qr-code-styling": "^1.9.2" }, diff --git a/public/compatTest.js b/public/compatTest.js index 91ae6e2e5..3a9a28c36 100644 --- a/public/compatTest.js +++ b/public/compatTest.js @@ -13,10 +13,11 @@ function compatTest() { var hasBigInt = typeof BigInt !== 'undefined'; var hasBroadcastChannel = typeof BroadcastChannel !== 'undefined'; var hasArrayAt = typeof new Array(0).at !== 'undefined'; + var hasDecompressionStream = typeof DecompressionStream !== 'undefined'; var isCompatible = hasPromise && hasWebSockets && hasWebCrypto && hasObjectFromEntries && hasResizeObserver && hasCssSupports && hasDisplayNames && hasPluralRules && hasNumberFormat && hasWebLocks && hasBigInt && hasBroadcastChannel - && hasArrayAt; + && hasArrayAt && hasDecompressionStream; if (isCompatible || (window.localStorage && window.localStorage.getItem('tt-ignore-compat'))) { window.isCompatTestPassed = true; @@ -38,6 +39,7 @@ function compatTest() { console.warn('BigInt', hasBigInt); console.warn('BroadcastChannel', hasBroadcastChannel); console.warn('Array.at', hasArrayAt); + console.warn('DecompressionStream', hasDecompressionStream); } // Hardcoded page because server forbids iframe embedding diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index fa8a8af93..fe3f988d7 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -111,10 +111,6 @@ declare module '*.strings' { export default url; } -declare module 'pako/dist/pako_inflate' { - function inflate(...args: any[]): string; -} - declare module 'opus-recorder' { export interface IOpusRecorder extends Omit { // eslint-disable-next-line @typescript-eslint/no-misused-new diff --git a/src/lib/gramjs/extensions/MessagePacker.ts b/src/lib/gramjs/extensions/MessagePacker.ts index df69fbd9c..dd4c68056 100644 --- a/src/lib/gramjs/extensions/MessagePacker.ts +++ b/src/lib/gramjs/extensions/MessagePacker.ts @@ -104,7 +104,7 @@ export default class MessagePacker { this.setReady?.(true); } - async getBeacon(state: RequestState) { + getBeacon(state: RequestState) { const buffer = new BinaryWriter(Buffer.alloc(0)); const size = state.data.length + TLMessage.SIZE_OVERHEAD; if (size <= MessageContainer.MAXIMUM_SIZE) { @@ -112,7 +112,7 @@ export default class MessagePacker { if (state.after) { afterId = state.after.msgId; } - state.msgId = await this._state.writeDataAsMessage( + state.msgId = this._state.writeDataAsMessage( buffer, state.data, state.request.classType === 'request', afterId, ); this._log.debug(`Assigned msgId = ${state.msgId.toString()} to ${state.request.className @@ -136,7 +136,7 @@ export default class MessagePacker { } } - async get() { + get() { if (!this._queue[this._queue.length - 1]) { this._queue = this._queue.filter(Boolean); return undefined; @@ -164,7 +164,7 @@ export default class MessagePacker { if (state.after) { afterId = state.after.msgId; } - state.msgId = await this._state.writeDataAsMessage( + state.msgId = this._state.writeDataAsMessage( buffer, state.data, state.request.classType === 'request', afterId, ); this._log.debug(`Assigned msgId = ${state.msgId.toString()} to ${state.request.className @@ -192,7 +192,7 @@ export default class MessagePacker { b.writeInt32LE(batch.length, 4); data = Buffer.concat([b, buffer.getValue()]); buffer = new BinaryWriter(Buffer.alloc(0)); - const containerId = await this._state.writeDataAsMessage( + const containerId = this._state.writeDataAsMessage( buffer, data, false, ); for (const s of batch) { diff --git a/src/lib/gramjs/network/MTProtoSender.ts b/src/lib/gramjs/network/MTProtoSender.ts index c0bf86aed..37b775bb0 100644 --- a/src/lib/gramjs/network/MTProtoSender.ts +++ b/src/lib/gramjs/network/MTProtoSender.ts @@ -431,7 +431,7 @@ export default class MTProtoSender { throw new Error('Cannot send requests while disconnected'); } const state = new RequestState(request, undefined); - const data = await this._sendQueue.getBeacon(state); + const data = this._sendQueue.getBeacon(state); if (!data) return; const encryptedData = await this._state.encryptMessageData(data); @@ -529,7 +529,7 @@ export default class MTProtoSender { && this.getConnection()!.shouldLongPoll) { await this._sendQueueLongPoll.wait(); - const res = await this._sendQueueLongPoll.get(); + const res = this._sendQueueLongPoll.get(); if (this.isReconnecting || !this._isFallback) { this._longPollLoopHandle = undefined; @@ -612,7 +612,7 @@ export default class MTProtoSender { // If we've had new ACKs appended while waiting for messages to send, add them to queue appendAcks(); - const res = await this._sendQueue.get(); + const res = this._sendQueue.get(); this.logWithIndex.debug(`Got ${res?.batch.length} message(s) to send`); diff --git a/src/lib/gramjs/network/MTProtoState.ts b/src/lib/gramjs/network/MTProtoState.ts index f106312a2..7c4a7947e 100644 --- a/src/lib/gramjs/network/MTProtoState.ts +++ b/src/lib/gramjs/network/MTProtoState.ts @@ -140,17 +140,17 @@ export default class MTProtoState { * @param contentRelated * @param afterId */ - async writeDataAsMessage( + writeDataAsMessage( buffer: BinaryWriter, data: Buffer, contentRelated: boolean, afterId?: bigint, - ): Promise { + ): bigint { const msgId = this._getNewMsgId(); const seqNo = this._getSeqNo(contentRelated); let body; if (afterId === undefined) { - body = await GZIPPacked.gzipIfSmaller(contentRelated, data); + body = GZIPPacked.gzipIfNeeded(contentRelated, data); } else { // Invoke query expects a query with a getBytes func - body = await GZIPPacked.gzipIfSmaller(contentRelated, new Api.InvokeAfterMsg({ + body = GZIPPacked.gzipIfNeeded(contentRelated, new Api.InvokeAfterMsg({ msgId: afterId, query: { getBytes() { diff --git a/src/lib/gramjs/tl/core/GZIPPacked.ts b/src/lib/gramjs/tl/core/GZIPPacked.ts index f856eb722..61667d750 100644 --- a/src/lib/gramjs/tl/core/GZIPPacked.ts +++ b/src/lib/gramjs/tl/core/GZIPPacked.ts @@ -1,65 +1,55 @@ -import { inflate } from 'pako/dist/pako_inflate'; +import { gzipSync, gunzipSync } from 'fflate'; import type { BinaryReader } from '../../extensions'; import { serializeBytes } from '..'; export default class GZIPPacked { - static CONSTRUCTOR_ID = 0x3072cfa1; + static CONSTRUCTOR_ID = 0x3072cfa1; - static classType = 'constructor'; + static classType = 'constructor'; - data: Buffer; + data: Buffer; - private CONSTRUCTOR_ID: number; + private CONSTRUCTOR_ID: number; - private classType: string; + private classType: string; - constructor(data: Buffer) { - this.data = data; - this.CONSTRUCTOR_ID = 0x3072cfa1; - this.classType = 'constructor'; + constructor(data: Buffer) { + this.data = data; + this.CONSTRUCTOR_ID = 0x3072cfa1; + this.classType = 'constructor'; + } + + static gzipIfNeeded(contentRelated: boolean, data: Buffer) { + if (contentRelated && data.length > 512) { + const gzipped = new GZIPPacked(data).toBytes(); + if (gzipped.length < data.length) { + return gzipped; + } } + return data; + } - static async gzipIfSmaller(contentRelated: boolean, data: Buffer) { - if (contentRelated && data.length > 512) { - const gzipped = await new GZIPPacked(data).toBytes(); - if (gzipped.length < data.length) { - return gzipped; - } - } - return data; - } + static gzip(input: Buffer) { + return Buffer.from(gzipSync(input)); + } - static gzip(input: Buffer) { - return Buffer.from(input); - // TODO this usually makes it faster for large requests - // return Buffer.from(deflate(input, { level: 9, gzip: true })) - } + static ungzip(input: Buffer) { + return Buffer.from(gunzipSync(input)); + } - static ungzip(input: Buffer) { - return Buffer.from(inflate(input)); - } + toBytes() { + const g = Buffer.alloc(4); + g.writeUInt32LE(GZIPPacked.CONSTRUCTOR_ID, 0); + return Buffer.concat([ + g, + serializeBytes(GZIPPacked.gzip(this.data)), + ]); + } - async toBytes() { - const g = Buffer.alloc(4); - g.writeUInt32LE(GZIPPacked.CONSTRUCTOR_ID, 0); - return Buffer.concat([ - g, - serializeBytes(await GZIPPacked.gzip(this.data)), - ]); - } - - static read(reader: BinaryReader) { - const constructor = reader.readInt(false); - if (constructor !== GZIPPacked.CONSTRUCTOR_ID) { - throw new Error('not equal'); - } - return GZIPPacked.gzip(reader.tgReadBytes()); - } - - static async fromReader(reader: BinaryReader) { - const data = reader.tgReadBytes(); - return new GZIPPacked(await GZIPPacked.ungzip(data)); - } + static async fromReader(reader: BinaryReader) { + const data = reader.tgReadBytes(); + return new GZIPPacked(GZIPPacked.ungzip(data)); + } } diff --git a/src/lib/rlottie/rlottie.worker.ts b/src/lib/rlottie/rlottie.worker.ts index e38780d3e..97ca7e1df 100644 --- a/src/lib/rlottie/rlottie.worker.ts +++ b/src/lib/rlottie/rlottie.worker.ts @@ -1,5 +1,3 @@ -import { inflate } from 'pako/dist/pako_inflate'; - import type { CancellableCallback } from '../../util/PostMessageConnector'; import { createWorkerInterface } from '../../util/createPostMessageInterface'; @@ -98,8 +96,13 @@ async function extractJson(tgsUrl: string) { return response.text(); } - const arrayBuffer = await response.arrayBuffer(); - return inflate(arrayBuffer, { to: 'string' }); + if (!response.body) { + return ''; + } + + const decompressionStream = response.body.pipeThrough(new DecompressionStream('gzip')); + const result = await new Response(decompressionStream).text(); + return result; } function calcParams(json: string, isLowPriority: boolean, framesCount: number) {