Change pako to fflate (#6699)

This commit is contained in:
zubiden 2026-02-22 23:43:28 +01:00 committed by Alexander Zinchuk
parent 03f163e528
commit be036138c3
10 changed files with 73 additions and 75 deletions

View File

@ -39,7 +39,7 @@ await invoke(new GramJs.help.GetAppConfig())
### Dependencies ### Dependencies
* [GramJS](https://github.com/gram-js/gramjs) ([MIT License](https://github.com/gram-js/gramjs/blob/master/LICENSE)) * [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)) * [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)) * [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)) * [twemoji-parser](https://github.com/twitter/twemoji-parser) ([MIT License](https://github.com/twitter/twemoji-parser/blob/master/LICENSE.md))

15
package-lock.json generated
View File

@ -18,12 +18,12 @@
"@tauri-apps/plugin-updater": "^2.9.0", "@tauri-apps/plugin-updater": "^2.9.0",
"async-mutex": "^0.5.0", "async-mutex": "^0.5.0",
"emoji-data-ios": "git+https://github.com/korenskoy/emoji-data-ios#443f1c9", "emoji-data-ios": "git+https://github.com/korenskoy/emoji-data-ios#443f1c9",
"fflate": "^0.8.2",
"idb-keyval": "^6.2.2", "idb-keyval": "^6.2.2",
"lowlight": "^3.3.0", "lowlight": "^3.3.0",
"music-metadata": "^11.11.1", "music-metadata": "^11.11.1",
"opus-recorder": "github:Ajaxy/opus-recorder#116830a", "opus-recorder": "github:Ajaxy/opus-recorder#116830a",
"os-browserify": "^0.3.0", "os-browserify": "^0.3.0",
"pako": "^2.1.0",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"qr-code-styling": "^1.9.2" "qr-code-styling": "^1.9.2"
}, },
@ -3213,9 +3213,9 @@
} }
}, },
"node_modules/@isaacs/brace-expansion": { "node_modules/@isaacs/brace-expansion": {
"version": "5.0.0", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz",
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "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": { "node_modules/file-entry-cache": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@ -16264,6 +16270,7 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
"dev": true,
"license": "(MIT AND Zlib)" "license": "(MIT AND Zlib)"
}, },
"node_modules/param-case": { "node_modules/param-case": {

View File

@ -132,12 +132,12 @@
"@tauri-apps/plugin-updater": "^2.9.0", "@tauri-apps/plugin-updater": "^2.9.0",
"async-mutex": "^0.5.0", "async-mutex": "^0.5.0",
"emoji-data-ios": "git+https://github.com/korenskoy/emoji-data-ios#443f1c9", "emoji-data-ios": "git+https://github.com/korenskoy/emoji-data-ios#443f1c9",
"fflate": "^0.8.2",
"idb-keyval": "^6.2.2", "idb-keyval": "^6.2.2",
"lowlight": "^3.3.0", "lowlight": "^3.3.0",
"music-metadata": "^11.11.1", "music-metadata": "^11.11.1",
"opus-recorder": "github:Ajaxy/opus-recorder#116830a", "opus-recorder": "github:Ajaxy/opus-recorder#116830a",
"os-browserify": "^0.3.0", "os-browserify": "^0.3.0",
"pako": "^2.1.0",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"qr-code-styling": "^1.9.2" "qr-code-styling": "^1.9.2"
}, },

View File

@ -13,10 +13,11 @@ function compatTest() {
var hasBigInt = typeof BigInt !== 'undefined'; var hasBigInt = typeof BigInt !== 'undefined';
var hasBroadcastChannel = typeof BroadcastChannel !== 'undefined'; var hasBroadcastChannel = typeof BroadcastChannel !== 'undefined';
var hasArrayAt = typeof new Array(0).at !== 'undefined'; var hasArrayAt = typeof new Array(0).at !== 'undefined';
var hasDecompressionStream = typeof DecompressionStream !== 'undefined';
var isCompatible = hasPromise && hasWebSockets && hasWebCrypto && hasObjectFromEntries && hasResizeObserver var isCompatible = hasPromise && hasWebSockets && hasWebCrypto && hasObjectFromEntries && hasResizeObserver
&& hasCssSupports && hasDisplayNames && hasPluralRules && hasNumberFormat && hasWebLocks && hasBigInt && hasBroadcastChannel && hasCssSupports && hasDisplayNames && hasPluralRules && hasNumberFormat && hasWebLocks && hasBigInt && hasBroadcastChannel
&& hasArrayAt; && hasArrayAt && hasDecompressionStream;
if (isCompatible || (window.localStorage && window.localStorage.getItem('tt-ignore-compat'))) { if (isCompatible || (window.localStorage && window.localStorage.getItem('tt-ignore-compat'))) {
window.isCompatTestPassed = true; window.isCompatTestPassed = true;
@ -38,6 +39,7 @@ function compatTest() {
console.warn('BigInt', hasBigInt); console.warn('BigInt', hasBigInt);
console.warn('BroadcastChannel', hasBroadcastChannel); console.warn('BroadcastChannel', hasBroadcastChannel);
console.warn('Array.at', hasArrayAt); console.warn('Array.at', hasArrayAt);
console.warn('DecompressionStream', hasDecompressionStream);
} }
// Hardcoded page because server forbids iframe embedding // Hardcoded page because server forbids iframe embedding

View File

@ -111,10 +111,6 @@ declare module '*.strings' {
export default url; export default url;
} }
declare module 'pako/dist/pako_inflate' {
function inflate(...args: any[]): string;
}
declare module 'opus-recorder' { declare module 'opus-recorder' {
export interface IOpusRecorder extends Omit<MediaRecorder, 'start' | 'ondataavailable'> { export interface IOpusRecorder extends Omit<MediaRecorder, 'start' | 'ondataavailable'> {
// eslint-disable-next-line @typescript-eslint/no-misused-new // eslint-disable-next-line @typescript-eslint/no-misused-new

View File

@ -104,7 +104,7 @@ export default class MessagePacker {
this.setReady?.(true); this.setReady?.(true);
} }
async getBeacon(state: RequestState) { getBeacon(state: RequestState) {
const buffer = new BinaryWriter(Buffer.alloc(0)); const buffer = new BinaryWriter(Buffer.alloc(0));
const size = state.data.length + TLMessage.SIZE_OVERHEAD; const size = state.data.length + TLMessage.SIZE_OVERHEAD;
if (size <= MessageContainer.MAXIMUM_SIZE) { if (size <= MessageContainer.MAXIMUM_SIZE) {
@ -112,7 +112,7 @@ export default class MessagePacker {
if (state.after) { if (state.after) {
afterId = state.after.msgId; afterId = state.after.msgId;
} }
state.msgId = await this._state.writeDataAsMessage( state.msgId = this._state.writeDataAsMessage(
buffer, state.data, state.request.classType === 'request', afterId, buffer, state.data, state.request.classType === 'request', afterId,
); );
this._log.debug(`Assigned msgId = ${state.msgId.toString()} to ${state.request.className 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]) { if (!this._queue[this._queue.length - 1]) {
this._queue = this._queue.filter(Boolean); this._queue = this._queue.filter(Boolean);
return undefined; return undefined;
@ -164,7 +164,7 @@ export default class MessagePacker {
if (state.after) { if (state.after) {
afterId = state.after.msgId; afterId = state.after.msgId;
} }
state.msgId = await this._state.writeDataAsMessage( state.msgId = this._state.writeDataAsMessage(
buffer, state.data, state.request.classType === 'request', afterId, buffer, state.data, state.request.classType === 'request', afterId,
); );
this._log.debug(`Assigned msgId = ${state.msgId.toString()} to ${state.request.className 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); b.writeInt32LE(batch.length, 4);
data = Buffer.concat([b, buffer.getValue()]); data = Buffer.concat([b, buffer.getValue()]);
buffer = new BinaryWriter(Buffer.alloc(0)); buffer = new BinaryWriter(Buffer.alloc(0));
const containerId = await this._state.writeDataAsMessage( const containerId = this._state.writeDataAsMessage(
buffer, data, false, buffer, data, false,
); );
for (const s of batch) { for (const s of batch) {

View File

@ -431,7 +431,7 @@ export default class MTProtoSender {
throw new Error('Cannot send requests while disconnected'); throw new Error('Cannot send requests while disconnected');
} }
const state = new RequestState(request, undefined); const state = new RequestState(request, undefined);
const data = await this._sendQueue.getBeacon(state); const data = this._sendQueue.getBeacon(state);
if (!data) return; if (!data) return;
const encryptedData = await this._state.encryptMessageData(data); const encryptedData = await this._state.encryptMessageData(data);
@ -529,7 +529,7 @@ export default class MTProtoSender {
&& this.getConnection()!.shouldLongPoll) { && this.getConnection()!.shouldLongPoll) {
await this._sendQueueLongPoll.wait(); await this._sendQueueLongPoll.wait();
const res = await this._sendQueueLongPoll.get(); const res = this._sendQueueLongPoll.get();
if (this.isReconnecting || !this._isFallback) { if (this.isReconnecting || !this._isFallback) {
this._longPollLoopHandle = undefined; 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 // If we've had new ACKs appended while waiting for messages to send, add them to queue
appendAcks(); appendAcks();
const res = await this._sendQueue.get(); const res = this._sendQueue.get();
this.logWithIndex.debug(`Got ${res?.batch.length} message(s) to send`); this.logWithIndex.debug(`Got ${res?.batch.length} message(s) to send`);

View File

@ -140,17 +140,17 @@ export default class MTProtoState {
* @param contentRelated * @param contentRelated
* @param afterId * @param afterId
*/ */
async writeDataAsMessage( writeDataAsMessage(
buffer: BinaryWriter, data: Buffer<ArrayBuffer>, contentRelated: boolean, afterId?: bigint, buffer: BinaryWriter, data: Buffer<ArrayBuffer>, contentRelated: boolean, afterId?: bigint,
): Promise<bigint> { ): bigint {
const msgId = this._getNewMsgId(); const msgId = this._getNewMsgId();
const seqNo = this._getSeqNo(contentRelated); const seqNo = this._getSeqNo(contentRelated);
let body; let body;
if (afterId === undefined) { if (afterId === undefined) {
body = await GZIPPacked.gzipIfSmaller(contentRelated, data); body = GZIPPacked.gzipIfNeeded(contentRelated, data);
} else { } else {
// Invoke query expects a query with a getBytes func // 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, msgId: afterId,
query: { query: {
getBytes() { getBytes() {

View File

@ -1,65 +1,55 @@
import { inflate } from 'pako/dist/pako_inflate'; import { gzipSync, gunzipSync } from 'fflate';
import type { BinaryReader } from '../../extensions'; import type { BinaryReader } from '../../extensions';
import { serializeBytes } from '..'; import { serializeBytes } from '..';
export default class GZIPPacked { export default class GZIPPacked {
static CONSTRUCTOR_ID = 0x3072cfa1; static CONSTRUCTOR_ID = 0x3072cfa1;
static classType = 'constructor'; static classType = 'constructor';
data: Buffer<ArrayBuffer>; data: Buffer<ArrayBuffer>;
private CONSTRUCTOR_ID: number; private CONSTRUCTOR_ID: number;
private classType: string; private classType: string;
constructor(data: Buffer<ArrayBuffer>) { constructor(data: Buffer<ArrayBuffer>) {
this.data = data; this.data = data;
this.CONSTRUCTOR_ID = 0x3072cfa1; this.CONSTRUCTOR_ID = 0x3072cfa1;
this.classType = 'constructor'; this.classType = 'constructor';
}
static gzipIfNeeded(contentRelated: boolean, data: Buffer<ArrayBuffer>) {
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<ArrayBuffer>) { static gzip(input: Buffer<ArrayBuffer>) {
if (contentRelated && data.length > 512) { return Buffer.from(gzipSync(input));
const gzipped = await new GZIPPacked(data).toBytes(); }
if (gzipped.length < data.length) {
return gzipped;
}
}
return data;
}
static gzip(input: Buffer<ArrayBuffer>) { static ungzip(input: Buffer) {
return Buffer.from(input); return Buffer.from(gunzipSync(input));
// TODO this usually makes it faster for large requests }
// return Buffer.from(deflate(input, { level: 9, gzip: true }))
}
static ungzip(input: Buffer) { toBytes() {
return Buffer.from(inflate(input)); const g = Buffer.alloc(4);
} g.writeUInt32LE(GZIPPacked.CONSTRUCTOR_ID, 0);
return Buffer.concat([
g,
serializeBytes(GZIPPacked.gzip(this.data)),
]);
}
async toBytes() { static async fromReader(reader: BinaryReader) {
const g = Buffer.alloc(4); const data = reader.tgReadBytes();
g.writeUInt32LE(GZIPPacked.CONSTRUCTOR_ID, 0); return new GZIPPacked(GZIPPacked.ungzip(data));
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));
}
} }

View File

@ -1,5 +1,3 @@
import { inflate } from 'pako/dist/pako_inflate';
import type { CancellableCallback } from '../../util/PostMessageConnector'; import type { CancellableCallback } from '../../util/PostMessageConnector';
import { createWorkerInterface } from '../../util/createPostMessageInterface'; import { createWorkerInterface } from '../../util/createPostMessageInterface';
@ -98,8 +96,13 @@ async function extractJson(tgsUrl: string) {
return response.text(); return response.text();
} }
const arrayBuffer = await response.arrayBuffer(); if (!response.body) {
return inflate(arrayBuffer, { to: 'string' }); 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) { function calcParams(json: string, isLowPriority: boolean, framesCount: number) {