auth-utils: Make a better addTransactionCapability function

This commit is contained in:
Rajeh Taher
2025-10-03 00:03:21 +03:00
parent 39541cc162
commit 1060f44bff
4 changed files with 299 additions and 379 deletions
+126
View File
@@ -0,0 +1,126 @@
import PQueue from 'p-queue'
import type { SignalDataSet, SignalDataTypeMap, SignalKeyStore } from '../Types'
import type { ILogger } from './logger'
/**
* Manages pre-key operations with proper concurrency control
*/
export class PreKeyManager {
private readonly queues = new Map<string, PQueue>()
constructor(
private readonly store: SignalKeyStore,
private readonly logger: ILogger
) {}
/**
* Get or create a queue for a specific key type
*/
private getQueue(keyType: string): PQueue {
if (!this.queues.has(keyType)) {
this.queues.set(keyType, new PQueue({ concurrency: 1 }))
}
return this.queues.get(keyType)!
}
/**
* Process pre-key operations (updates and deletions)
*/
async processOperations(
data: SignalDataSet,
keyType: keyof SignalDataTypeMap,
transactionCache: SignalDataSet,
mutations: SignalDataSet,
isInTransaction: boolean
): Promise<void> {
const keyData = data[keyType]
if (!keyData) return
return this.getQueue(keyType).add(async () => {
// Ensure structures exist
transactionCache[keyType] = transactionCache[keyType] || ({} as any)
mutations[keyType] = mutations[keyType] || ({} as any)
// Separate deletions from updates
const deletions: string[] = []
const updates: Record<string, any> = {}
for (const keyId in keyData) {
if (keyData[keyId] === null) {
deletions.push(keyId)
} else {
updates[keyId] = keyData[keyId]
}
}
// Process updates (no validation needed)
if (Object.keys(updates).length > 0) {
Object.assign(transactionCache[keyType]!, updates)
Object.assign(mutations[keyType]!, updates)
}
// Process deletions with validation
if (deletions.length > 0) {
await this.processDeletions(keyType, deletions, transactionCache, mutations, isInTransaction)
}
})
}
/**
* Process deletions with validation
*/
private async processDeletions(
keyType: keyof SignalDataTypeMap,
ids: string[],
transactionCache: SignalDataSet,
mutations: SignalDataSet,
isInTransaction: boolean
): Promise<void> {
if (isInTransaction) {
// In transaction, only allow deletion if key exists in cache
for (const keyId of ids) {
if (transactionCache[keyType]?.[keyId]) {
transactionCache[keyType][keyId] = null
mutations[keyType]![keyId] = null
} else {
this.logger.warn(`Skipping deletion of non-existent ${keyType} in transaction: ${keyId}`)
}
}
} else {
// Outside transaction, validate against store
const existingKeys = await this.store.get(keyType, ids)
for (const keyId of ids) {
if (existingKeys[keyId]) {
transactionCache[keyType]![keyId] = null
mutations[keyType]![keyId] = null
} else {
this.logger.warn(`Skipping deletion of non-existent ${keyType}: ${keyId}`)
}
}
}
}
/**
* Validate and process pre-key deletions outside transactions
*/
async validateDeletions(data: SignalDataSet, keyType: keyof SignalDataTypeMap): Promise<void> {
const keyData = data[keyType]
if (!keyData) return
return this.getQueue(keyType).add(async () => {
// Find all deletion requests
const deletionIds = Object.keys(keyData).filter(id => keyData[id] === null)
if (deletionIds.length === 0) return
// Validate deletions
const existingKeys = await this.store.get(keyType, deletionIds)
for (const keyId of deletionIds) {
if (!existingKeys[keyId]) {
this.logger.warn(`Skipping deletion of non-existent ${keyType}: ${keyId}`)
delete data[keyType]![keyId]
}
}
})
}
}