mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
Add pack icon system and E2EE removal plan
- Replace emoji icons with Ionicons for professional appearance - Improve E2EE key recovery with better diagnostics - Add analytics for pack icon events - Document E2EE removal plan for future simplification 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
953bd7c03d
commit
43fa7033dc
14 files changed
+2013
-961
No files matched your search
@@ -10,15 +10,23 @@ import type { RSAPublicKeyJWK, RSAPrivateKeyJWK } from "../lib/encryption/types"
|
||||
* Encryption key state - stored globally to ensure all components
|
||||
* share the same key state and avoid race conditions during encryption.
|
||||
*/
|
||||
interface EncryptionKeyState {
|
||||
privateKeyJwk: RSAPrivateKeyJWK | null;
|
||||
publicKeyJwk: RSAPublicKeyJWK | null;
|
||||
hasKeys: boolean;
|
||||
isLoadingKeys: boolean;
|
||||
keysError: Error | null;
|
||||
/** Incremented when keys change - consumers can use this to trigger re-renders */
|
||||
keysVersion: number;
|
||||
}
|
||||
type EncryptionRecoveryReason = 'stale-key' | 'pending-recipient' | 'rotation' | 'keys-unavailable';
|
||||
|
||||
interface EncryptionKeyState {
|
||||
privateKeyJwk: RSAPrivateKeyJWK | null;
|
||||
publicKeyJwk: RSAPublicKeyJWK | null;
|
||||
hasKeys: boolean;
|
||||
isLoadingKeys: boolean;
|
||||
keysError: Error | null;
|
||||
/** Incremented when keys change - consumers can use this to trigger re-renders */
|
||||
keysVersion: number;
|
||||
/** Background recovery status for E2EE issues */
|
||||
isRecovering: boolean;
|
||||
recoveryReason: EncryptionRecoveryReason | null;
|
||||
recoveryCount: number;
|
||||
recoveryUpdatedAt: number | null;
|
||||
}
|
||||
|
||||
|
||||
interface AuthState {
|
||||
user: Profile | null;
|
||||
@@ -37,9 +45,12 @@ interface AuthState {
|
||||
signOut: () => Promise<void>;
|
||||
setUser: (user: Profile | null) => void;
|
||||
|
||||
// Encryption key actions
|
||||
setEncryptionKeys: (keys: Partial<EncryptionKeyState>) => void;
|
||||
clearEncryptionKeys: () => void;
|
||||
// Encryption key actions
|
||||
setEncryptionKeys: (keys: Partial<EncryptionKeyState>) => void;
|
||||
clearEncryptionKeys: () => void;
|
||||
beginEncryptionRecovery: (reason: EncryptionRecoveryReason) => void;
|
||||
endEncryptionRecovery: () => void;
|
||||
|
||||
}
|
||||
|
||||
// Import other stores lazily to avoid circular dependency issues
|
||||
@@ -51,14 +62,19 @@ const getOtherStores = () => {
|
||||
return { useMatchStore, usePacksStore, useMessageStore, useSubscriptionStore };
|
||||
};
|
||||
|
||||
const initialEncryptionKeyState: EncryptionKeyState = {
|
||||
privateKeyJwk: null,
|
||||
publicKeyJwk: null,
|
||||
hasKeys: false,
|
||||
isLoadingKeys: true,
|
||||
keysError: null,
|
||||
keysVersion: 0,
|
||||
};
|
||||
const initialEncryptionKeyState: EncryptionKeyState = {
|
||||
privateKeyJwk: null,
|
||||
publicKeyJwk: null,
|
||||
hasKeys: false,
|
||||
isLoadingKeys: true,
|
||||
keysError: null,
|
||||
keysVersion: 0,
|
||||
isRecovering: false,
|
||||
recoveryReason: null,
|
||||
recoveryCount: 0,
|
||||
recoveryUpdatedAt: null,
|
||||
};
|
||||
|
||||
|
||||
export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
user: null,
|
||||
@@ -235,23 +251,55 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
* Update encryption key state. This is the ONLY way to update keys
|
||||
* to ensure all consumers see the same state.
|
||||
*/
|
||||
setEncryptionKeys: (keys) => {
|
||||
set((state) => ({
|
||||
encryptionKeys: {
|
||||
...state.encryptionKeys,
|
||||
...keys,
|
||||
// Increment version when keys actually change
|
||||
keysVersion: (keys.publicKeyJwk !== undefined || keys.privateKeyJwk !== undefined)
|
||||
? state.encryptionKeys.keysVersion + 1
|
||||
: state.encryptionKeys.keysVersion,
|
||||
},
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear encryption keys (called on sign out or when leaving couple)
|
||||
*/
|
||||
clearEncryptionKeys: () => {
|
||||
set({ encryptionKeys: initialEncryptionKeyState });
|
||||
},
|
||||
}));
|
||||
setEncryptionKeys: (keys) => {
|
||||
set((state) => ({
|
||||
encryptionKeys: {
|
||||
...state.encryptionKeys,
|
||||
...keys,
|
||||
// Increment version when keys actually change
|
||||
keysVersion: (keys.publicKeyJwk !== undefined || keys.privateKeyJwk !== undefined)
|
||||
? state.encryptionKeys.keysVersion + 1
|
||||
: state.encryptionKeys.keysVersion,
|
||||
},
|
||||
}));
|
||||
},
|
||||
|
||||
beginEncryptionRecovery: (reason) => {
|
||||
set((state) => {
|
||||
const nextCount = state.encryptionKeys.recoveryCount + 1;
|
||||
return {
|
||||
encryptionKeys: {
|
||||
...state.encryptionKeys,
|
||||
recoveryCount: nextCount,
|
||||
isRecovering: true,
|
||||
recoveryReason: reason,
|
||||
recoveryUpdatedAt: Date.now(),
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
endEncryptionRecovery: () => {
|
||||
set((state) => {
|
||||
const nextCount = Math.max(0, state.encryptionKeys.recoveryCount - 1);
|
||||
const isRecovering = nextCount > 0;
|
||||
return {
|
||||
encryptionKeys: {
|
||||
...state.encryptionKeys,
|
||||
recoveryCount: nextCount,
|
||||
isRecovering,
|
||||
recoveryReason: isRecovering ? state.encryptionKeys.recoveryReason : null,
|
||||
recoveryUpdatedAt: Date.now(),
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear encryption keys (called on sign out or when leaving couple)
|
||||
*/
|
||||
clearEncryptionKeys: () => {
|
||||
set({ encryptionKeys: initialEncryptionKeyState });
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in new issue
Block a user