fix: sync pre-keys on socket start and add specific pre-key error approach (#1663)

* fix: sync prekeys when storage mismatch

* fix: verify if current pre-key exists instead of matching

* add: pre-keys error specific approach

* fix: lint

* fix: lint
This commit is contained in:
Gustavo Quadri
2025-09-07 08:11:50 -03:00
committed by GitHub
parent 0fbce52fd3
commit 8f21a67e4a
3 changed files with 148 additions and 19 deletions
+103 -13
View File
@@ -8,7 +8,9 @@ import {
DEF_TAG_PREFIX,
INITIAL_PREKEY_COUNT,
MIN_PREKEY_COUNT,
NOISE_WA_HEADER
MIN_UPLOAD_INTERVAL,
NOISE_WA_HEADER,
UPLOAD_TIMEOUT
} from '../Defaults'
import { cleanupQueues } from '../Signal/Group/queue-job'
import type { SocketConfig } from '../Types'
@@ -298,24 +300,112 @@ export const makeSocket = (config: SocketConfig) => {
return +countChild.attrs.value!
}
// Pre-key upload state management
let uploadPreKeysPromise: Promise<void> | null = null
let lastUploadTime = 0
/** generates and uploads a set of pre-keys to the server */
const uploadPreKeys = async (count = INITIAL_PREKEY_COUNT) => {
await keys.transaction(async () => {
logger.info({ count }, 'uploading pre-keys')
const { update, node } = await getNextPreKeysNode({ creds, keys }, count)
const uploadPreKeys = async (count = INITIAL_PREKEY_COUNT, retryCount = 0) => {
// Check minimum interval (except for retries)
if (retryCount === 0) {
const timeSinceLastUpload = Date.now() - lastUploadTime
if (timeSinceLastUpload < MIN_UPLOAD_INTERVAL) {
logger.debug(`Skipping upload, only ${timeSinceLastUpload}ms since last upload`)
return
}
}
await query(node)
ev.emit('creds.update', update)
// Prevent multiple concurrent uploads
if (uploadPreKeysPromise) {
logger.debug('Pre-key upload already in progress, waiting for completion')
return uploadPreKeysPromise
}
logger.info({ count }, 'uploaded pre-keys')
})
const uploadLogic = async () => {
logger.info({ count, retryCount }, 'uploading pre-keys')
// Generate and save pre-keys atomically (prevents ID collisions on retry)
const node = await keys.transaction(async () => {
logger.debug({ requestedCount: count }, 'generating pre-keys with requested count')
const { update, node } = await getNextPreKeysNode({ creds, keys }, count)
// Update credentials immediately to prevent duplicate IDs on retry
ev.emit('creds.update', update)
return node // Only return node since update is already used
})
// Upload to server (outside transaction, can fail without affecting local keys)
try {
await query(node)
logger.info({ count }, 'uploaded pre-keys successfully')
lastUploadTime = Date.now()
} catch (uploadError) {
logger.error({ uploadError, count }, 'Failed to upload pre-keys to server')
// Exponential backoff retry (max 3 retries)
if (retryCount < 3) {
const backoffDelay = Math.min(1000 * Math.pow(2, retryCount), 10000)
logger.info(`Retrying pre-key upload in ${backoffDelay}ms`)
await new Promise(resolve => setTimeout(resolve, backoffDelay))
return uploadPreKeys(count, retryCount + 1)
}
throw uploadError
}
}
// Add timeout protection
uploadPreKeysPromise = Promise.race([
uploadLogic(),
new Promise<void>((_, reject) =>
setTimeout(() => reject(new Boom('Pre-key upload timeout', { statusCode: 408 })), UPLOAD_TIMEOUT)
)
])
try {
await uploadPreKeysPromise
} finally {
uploadPreKeysPromise = null
}
}
const verifyCurrentPreKeyExists = async () => {
const currentPreKeyId = creds.nextPreKeyId - 1
if (currentPreKeyId <= 0) {
return { exists: false, currentPreKeyId: 0 }
}
const preKeys = await keys.get('pre-key', [currentPreKeyId.toString()])
const exists = !!preKeys[currentPreKeyId.toString()]
return { exists, currentPreKeyId }
}
const uploadPreKeysToServerIfRequired = async () => {
const preKeyCount = await getAvailablePreKeysOnServer()
logger.info(`${preKeyCount} pre-keys found on server`)
if (preKeyCount <= MIN_PREKEY_COUNT) {
await uploadPreKeys()
try {
const preKeyCount = await getAvailablePreKeysOnServer()
const { exists: currentPreKeyExists, currentPreKeyId } = await verifyCurrentPreKeyExists()
logger.info(`${preKeyCount} pre-keys found on server`)
logger.info(`Current prekey ID: ${currentPreKeyId}, exists in storage: ${currentPreKeyExists}`)
const lowServerCount = preKeyCount <= MIN_PREKEY_COUNT
const missingCurrentPreKey = !currentPreKeyExists && currentPreKeyId > 0
const shouldUpload = lowServerCount || missingCurrentPreKey
if (shouldUpload) {
const reasons = []
if (lowServerCount) reasons.push(`server count low (${preKeyCount})`)
if (missingCurrentPreKey) reasons.push(`current prekey ${currentPreKeyId} missing from storage`)
logger.info(`Uploading PreKeys due to: ${reasons.join(', ')}`)
await uploadPreKeys()
} else {
logger.info(`PreKey validation passed - Server: ${preKeyCount}, Current prekey ${currentPreKeyId} exists`)
}
} catch (error) {
logger.error({ error }, 'Failed to check/upload pre-keys during initialization')
// Don't throw - allow connection to continue even if pre-key check fails
}
}