feat: implement auto-fix for one-time key conflicts in crypto database
This commit is contained in:
@@ -550,7 +550,7 @@ export function ThreadView({ room, threadRootId }: ThreadViewProps) {
|
||||
hour24Clock={hour24Clock}
|
||||
dateFormatString={dateFormatString}
|
||||
reply={
|
||||
replyEventId ? (
|
||||
replyEventId && replyEventId !== threadRootId ? (
|
||||
<Reply
|
||||
room={room}
|
||||
timelineSet={roomTimelineSet}
|
||||
|
||||
@@ -4,6 +4,9 @@ import { cryptoCallbacks } from './secretStorageKeys';
|
||||
import { clearNavToActivePathStore } from '../app/state/navToActivePath';
|
||||
import { Session, getSessionStoreName } from '../app/state/sessions';
|
||||
|
||||
// Track if we're currently handling a key conflict to prevent loops
|
||||
let handlingKeyConflict = false;
|
||||
|
||||
const deleteIndexedDB = (dbName: string): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = global.indexedDB.deleteDatabase(dbName);
|
||||
@@ -73,9 +76,129 @@ const deleteAllMatrixDatabases = async (): Promise<void> => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete the Rust crypto database to fix one-time key conflicts.
|
||||
* This happens when the local crypto state has keys that conflict with server state.
|
||||
*/
|
||||
const deleteRustCryptoDatabase = async (session: Session): Promise<boolean> => {
|
||||
console.log('[initMatrix] 🔑 Attempting to delete Rust crypto database to fix key conflicts...');
|
||||
|
||||
const dbNames = await getAllIndexedDBNames();
|
||||
const storagePrefix = `${session.userId}@${session.deviceId}`;
|
||||
|
||||
// Find crypto databases for this session
|
||||
const cryptoDbNames = dbNames.filter(name =>
|
||||
name.includes('matrix-sdk-crypto') ||
|
||||
name.includes(storagePrefix)
|
||||
);
|
||||
|
||||
if (cryptoDbNames.length === 0) {
|
||||
console.log('[initMatrix] No crypto databases found to delete');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('[initMatrix] 🗑️ Deleting crypto databases:', cryptoDbNames);
|
||||
|
||||
for (const dbName of cryptoDbNames) {
|
||||
try {
|
||||
await deleteIndexedDB(dbName);
|
||||
console.log(`[initMatrix] ✅ Deleted: ${dbName}`);
|
||||
} catch (error) {
|
||||
console.error(`[initMatrix] ❌ Failed to delete ${dbName}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup error listener to detect one-time key conflicts and auto-fix them.
|
||||
*/
|
||||
const setupKeyConflictHandler = (mx: MatrixClient, session: Session): void => {
|
||||
// Listen for HTTP errors from the client
|
||||
let keyConflictCount = 0;
|
||||
const KEY_CONFLICT_THRESHOLD = 3;
|
||||
|
||||
// Patch console.log/error to detect the key conflict errors
|
||||
const originalConsoleLog = console.log;
|
||||
const keyConflictPattern = /One time key.*already exists/;
|
||||
|
||||
// We need to use a different approach - listen to the crypto module
|
||||
// Since we can't directly intercept, we'll use a MutationObserver-style approach
|
||||
// by checking for repeated key upload failures
|
||||
|
||||
const checkKeyConflict = (args: any[]): boolean => {
|
||||
const message = args.map(a => String(a)).join(' ');
|
||||
return keyConflictPattern.test(message);
|
||||
};
|
||||
|
||||
// Override console methods temporarily to detect the errors
|
||||
const originalError = console.error;
|
||||
const originalWarn = console.warn;
|
||||
|
||||
const errorHandler = (...args: any[]) => {
|
||||
if (checkKeyConflict(args)) {
|
||||
keyConflictCount++;
|
||||
console.log(`[initMatrix] ⚠️ Detected key conflict (${keyConflictCount}/${KEY_CONFLICT_THRESHOLD})`);
|
||||
|
||||
if (keyConflictCount >= KEY_CONFLICT_THRESHOLD && !handlingKeyConflict) {
|
||||
handlingKeyConflict = true;
|
||||
console.log('[initMatrix] 🔄 Too many key conflicts detected, attempting auto-fix...');
|
||||
|
||||
// Schedule the fix for next page load to avoid issues during active session
|
||||
localStorage.setItem('cinny_fix_key_conflict', JSON.stringify({
|
||||
userId: session.userId,
|
||||
deviceId: session.deviceId,
|
||||
timestamp: Date.now()
|
||||
}));
|
||||
|
||||
// Restore console methods
|
||||
console.error = originalError;
|
||||
console.warn = originalWarn;
|
||||
|
||||
// Notify user and reload
|
||||
setTimeout(() => {
|
||||
if (confirm('Detected encryption key conflict. The app needs to reset your encryption session. You may need to re-verify your devices after this. Continue?')) {
|
||||
deleteRustCryptoDatabase(session).then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
} else {
|
||||
handlingKeyConflict = false;
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
originalError.apply(console, args);
|
||||
};
|
||||
|
||||
console.error = errorHandler;
|
||||
console.warn = (...args: any[]) => {
|
||||
if (checkKeyConflict(args)) {
|
||||
keyConflictCount++;
|
||||
}
|
||||
originalWarn.apply(console, args);
|
||||
};
|
||||
};
|
||||
|
||||
export const initClient = async (session: Session): Promise<MatrixClient> => {
|
||||
const storeName = getSessionStoreName(session);
|
||||
|
||||
// Check if we need to fix a key conflict from a previous session
|
||||
const pendingFix = localStorage.getItem('cinny_fix_key_conflict');
|
||||
if (pendingFix) {
|
||||
try {
|
||||
const fixData = JSON.parse(pendingFix);
|
||||
if (fixData.userId === session.userId && fixData.deviceId === session.deviceId) {
|
||||
console.log('[initMatrix] 🔧 Found pending key conflict fix, clearing crypto database...');
|
||||
await deleteRustCryptoDatabase(session);
|
||||
localStorage.removeItem('cinny_fix_key_conflict');
|
||||
console.log('[initMatrix] ✅ Crypto database cleared');
|
||||
}
|
||||
} catch (e) {
|
||||
localStorage.removeItem('cinny_fix_key_conflict');
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[initMatrix] ========================================');
|
||||
console.log('[initMatrix] Initializing Matrix client for session:');
|
||||
console.log('[initMatrix] User ID:', session.userId);
|
||||
@@ -153,6 +276,9 @@ export const initClient = async (session: Session): Promise<MatrixClient> => {
|
||||
return user;
|
||||
};
|
||||
|
||||
// Setup handler to detect and auto-fix one-time key conflicts
|
||||
setupKeyConflictHandler(mx, session);
|
||||
|
||||
return mx;
|
||||
} catch (error: any) {
|
||||
// Handle crypto store account mismatch error
|
||||
@@ -188,6 +314,51 @@ export const clearCacheAndReload = async (mx: MatrixClient) => {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
/**
|
||||
* Fix one-time key conflicts by clearing the Rust crypto database.
|
||||
* Call this when you see repeated "One time key already exists" errors.
|
||||
* The user will need to re-verify their devices after this.
|
||||
*/
|
||||
export const fixKeyConflict = async (mx: MatrixClient): Promise<void> => {
|
||||
const userId = mx.getSafeUserId();
|
||||
const deviceId = mx.getDeviceId();
|
||||
|
||||
if (!userId || !deviceId) {
|
||||
console.error('[initMatrix] Cannot fix key conflict: missing userId or deviceId');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[initMatrix] 🔧 Fixing key conflict for', userId, deviceId);
|
||||
|
||||
// Stop the client first
|
||||
mx.stopClient();
|
||||
|
||||
// Delete the crypto databases
|
||||
const dbNames = await getAllIndexedDBNames();
|
||||
const storagePrefix = `${userId}@${deviceId}`;
|
||||
|
||||
const cryptoDbNames = dbNames.filter(name =>
|
||||
name.includes('matrix-sdk-crypto') ||
|
||||
name.includes(storagePrefix) ||
|
||||
name.includes('crypto')
|
||||
);
|
||||
|
||||
console.log('[initMatrix] 🗑️ Deleting crypto databases:', cryptoDbNames);
|
||||
|
||||
for (const dbName of cryptoDbNames) {
|
||||
try {
|
||||
await deleteIndexedDB(dbName);
|
||||
console.log(`[initMatrix] ✅ Deleted: ${dbName}`);
|
||||
} catch (error) {
|
||||
console.error(`[initMatrix] ❌ Failed to delete ${dbName}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Reload the page
|
||||
console.log('[initMatrix] 🔄 Reloading...');
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
export const logoutClient = async (mx: MatrixClient) => {
|
||||
mx.stopClient();
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user