9525f42cb2
* perf: optimize history sync memory and CPU usage Aligns with Baileys upstream PR #2333 . Surgical port of 4 micro-optimizations + 2 test files. No collision with InfiniteAPI's LID/PN, carousel, buttons, Bad MAC or app state sync resilience code. Changes: 1. WAProto longToString/longToNumber: use native BigInt instead of Long library division loops. ~4.6x faster per call (237ns -> 52ns). BigInt.toString() delegates to V8 native C++. Same change applied to fix-imports.js (template) and index.js (generated artifact) so future regenerations stay in sync. 2. downloadHistory: stream-pipe createInflate instead of buffering the full compressed payload then inflating. Cuts RSS peak ~50% on 50MB history payloads. 3. processHistoryMessage: drop the { ...chat } shallow copy before pushing to chats[]. The decoded protobuf object is not referenced after the push. 4. downloadEncryptedContent transform: skip Buffer.concat when remainingBytes is empty (the common case — chunks usually arrive AES-aligned). ~19x faster per chunk (78ms -> 4ms over 25k chunks). Tests: - bigint-validation.test.ts (NEW): 32 parameterized cases covering zero, MAX_SAFE_INTEGER boundary, max int64/uint64, negative values, tricky bit patterns and powers of 2; plus 200-iter fuzz comparing old Long vs new BigInt implementations for equivalence. - proto-tojson-long.test.ts: kept the existing fork-only test for string-in-Long-field graceful handling, appended 4 upstream tests covering Long->string fast path, large unsigned > MAX_SAFE_INTEGER, zero/small values and encode/decode roundtrip.
90 lines
3.9 KiB
JavaScript
90 lines
3.9 KiB
JavaScript
import { readFileSync, writeFileSync } from 'fs';
|
|
import { exit } from 'process';
|
|
|
|
const filePath = './index.js'
|
|
|
|
try {
|
|
let content = readFileSync(filePath, 'utf8')
|
|
|
|
content = content.replace(/import \* as (\$protobuf) from/g, 'import $1 from')
|
|
content = content.replace(/(['"])protobufjs\/minimal(['"])/g, '$1protobufjs/minimal.js$2')
|
|
|
|
const marker = 'const $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {});\n\n'
|
|
const longToStringHelper =
|
|
'function longToString(value, unsigned) {\n' +
|
|
'\tif (typeof value === "string") {\n' +
|
|
'\t\treturn value;\n' +
|
|
'\t}\n' +
|
|
'\tif (typeof value === "number") {\n' +
|
|
'\t\treturn String(value);\n' +
|
|
'\t}\n' +
|
|
'\t// Fast path: convert Long {low, high} directly via native BigInt\n' +
|
|
'\t// BigInt.toString() is a native C++ operation, much faster than Long\'s pure JS division loops\n' +
|
|
'\tif (value && typeof value.low === "number" && typeof value.high === "number") {\n' +
|
|
'\t\t// Normalize high to signed int32 so the sign-bit check works for inputs\n' +
|
|
'\t\t// where high is stored unsigned (e.g. raw {low, high} JSON).\n' +
|
|
'\t\tconst high = value.high | 0;\n' +
|
|
'\t\tconst lo = BigInt(value.low >>> 0);\n' +
|
|
'\t\tconst hi = BigInt(value.high >>> 0);\n' +
|
|
'\t\tconst combined = (hi << 32n) | lo;\n' +
|
|
'\t\tif (!unsigned && high < 0) {\n' +
|
|
'\t\t\treturn (combined - (1n << 64n)).toString();\n' +
|
|
'\t\t}\n' +
|
|
'\t\treturn combined.toString();\n' +
|
|
'\t}\n' +
|
|
'\treturn String(value);\n' +
|
|
'}\n\n'
|
|
const longToNumberHelper =
|
|
'function longToNumber(value, unsigned) {\n' +
|
|
'\tif (typeof value === "number") {\n' +
|
|
'\t\treturn value;\n' +
|
|
'\t}\n' +
|
|
'\tif (typeof value === "string") {\n' +
|
|
'\t\treturn Number(value);\n' +
|
|
'\t}\n' +
|
|
'\t// Fast path: convert Long {low, high} directly via native BigInt\n' +
|
|
'\tif (value && typeof value.low === "number" && typeof value.high === "number") {\n' +
|
|
'\t\tconst high = value.high | 0;\n' +
|
|
'\t\tconst lo = BigInt(value.low >>> 0);\n' +
|
|
'\t\tconst hi = BigInt(value.high >>> 0);\n' +
|
|
'\t\tconst combined = (hi << 32n) | lo;\n' +
|
|
'\t\tif (!unsigned && high < 0) {\n' +
|
|
'\t\t\treturn Number(combined - (1n << 64n));\n' +
|
|
'\t\t}\n' +
|
|
'\t\treturn Number(combined);\n' +
|
|
'\t}\n' +
|
|
'\treturn Number(value);\n' +
|
|
'}\n\n'
|
|
|
|
if (!content.includes('function longToString(')) {
|
|
const markerIndex = content.indexOf(marker)
|
|
if (markerIndex === -1) {
|
|
throw new Error('Unable to inject Long helpers: marker not found in WAProto index output')
|
|
}
|
|
|
|
content = content.replace(marker, `${marker}${longToStringHelper}${longToNumberHelper}`)
|
|
} else {
|
|
const longToStringRegex = /function longToString\(value, unsigned\) {\n[\s\S]*?\n}\n\n/
|
|
const longToNumberRegex = /function longToNumber\(value, unsigned\) {\n[\s\S]*?\n}\n\n/
|
|
|
|
if (!longToStringRegex.test(content) || !longToNumberRegex.test(content)) {
|
|
throw new Error('Unable to update Long helpers: existing definitions not found')
|
|
}
|
|
|
|
content = content.replace(longToStringRegex, longToStringHelper)
|
|
content = content.replace(longToNumberRegex, longToNumberHelper)
|
|
}
|
|
|
|
const longPattern = /([ \t]+d\.(\w+) = )o\.longs === String \? \$util\.Long\.prototype\.toString\.call\(m\.\2\) : o\.longs === Number \? new \$util\.LongBits\(m\.\2\.low >>> 0, m\.\2\.high >>> 0\)\.toNumber\((true)?\) : m\.\2;/g
|
|
content = content.replace(longPattern, (_match, prefix, field, unsignedFlag) => {
|
|
const unsignedArg = unsignedFlag ? ', true' : ''
|
|
return `${prefix}o.longs === String ? longToString(m.${field}${unsignedArg}) : o.longs === Number ? longToNumber(m.${field}${unsignedArg}) : m.${field};`
|
|
})
|
|
|
|
writeFileSync(filePath, content, 'utf8')
|
|
console.log(`✅ Fixed imports in ${filePath}`)
|
|
} catch (error) {
|
|
console.error(`❌ Error fixing imports: ${error.message}`)
|
|
exit(1)
|
|
}
|