mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-11-02 02: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
@@ -8,6 +8,12 @@ export async function initAnalytics() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip initialization in non-RN environments (e.g., during tsc/Node.js)
|
||||
// AsyncStorage requires window to be defined
|
||||
if (typeof window === "undefined" && typeof global !== "undefined" && !("__BUNDLE_START_TIME__" in global)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const apiKey = process.env.EXPO_PUBLIC_POSTHOG_API_KEY;
|
||||
const host = process.env.EXPO_PUBLIC_POSTHOG_HOST || "https://us.i.posthog.com";
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ export {
|
||||
} from './utils';
|
||||
|
||||
// Key Rotation
|
||||
export { triggerKeyRotation } from './triggerKeyRotation';
|
||||
export { triggerKeyRotation, triggerAutoKeyRotation } from './triggerKeyRotation';
|
||||
|
||||
// Stale Key Repair
|
||||
export { repairStaleKey } from './repairStaleKey';
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
* the user's new public key.
|
||||
*/
|
||||
|
||||
import { supabase } from '../supabase';
|
||||
import { supabase } from '../supabase';
|
||||
import { reencryptPendingMessages } from './reencryptPendingMessages';
|
||||
|
||||
|
||||
export interface KeyRotationResult {
|
||||
success: boolean;
|
||||
@@ -18,55 +20,83 @@ export interface KeyRotationResult {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
let inFlight: Promise<KeyRotationResult | null> | null = null;
|
||||
let inFlight: Promise<KeyRotationResult | null> | null = null;
|
||||
let autoRotationInFlight: Promise<KeyRotationResult | null> | null = null;
|
||||
let lastAutoRotationAt = 0;
|
||||
|
||||
const AUTO_ROTATION_MIN_INTERVAL_MS = 60_000;
|
||||
|
||||
|
||||
export async function triggerKeyRotation(): Promise<KeyRotationResult | null> {
|
||||
// Prevent concurrent calls
|
||||
if (inFlight) return inFlight;
|
||||
|
||||
inFlight = (async () => {
|
||||
try {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session?.access_token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const baseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL;
|
||||
if (!baseUrl) {
|
||||
return { success: false, updated: 0, error: 'Missing EXPO_PUBLIC_SUPABASE_URL' };
|
||||
}
|
||||
|
||||
console.log('[E2EE] Triggering key rotation for regenerated keys...');
|
||||
|
||||
const response = await fetch(`${baseUrl}/functions/v1/handle-key-rotation`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.access_token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
console.error('[E2EE] Key rotation failed:', text);
|
||||
return { success: false, updated: 0, error: text || 'Failed to rotate keys' };
|
||||
}
|
||||
|
||||
const result = (await response.json()) as KeyRotationResult;
|
||||
console.log(`[E2EE] Key rotation complete: ${result.updated} messages updated`);
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.error('[E2EE] Key rotation error:', err);
|
||||
return {
|
||||
success: false,
|
||||
updated: 0,
|
||||
error: err instanceof Error ? err.message : 'Unknown error',
|
||||
};
|
||||
} finally {
|
||||
inFlight = null;
|
||||
}
|
||||
})();
|
||||
|
||||
return inFlight;
|
||||
}
|
||||
export async function triggerKeyRotation(): Promise<KeyRotationResult | null> {
|
||||
// Prevent concurrent calls
|
||||
if (inFlight) return inFlight;
|
||||
|
||||
inFlight = (async () => {
|
||||
try {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session?.access_token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const baseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL;
|
||||
if (!baseUrl) {
|
||||
return { success: false, updated: 0, error: 'Missing EXPO_PUBLIC_SUPABASE_URL' };
|
||||
}
|
||||
|
||||
console.log('[E2EE] Triggering key rotation for regenerated keys...');
|
||||
|
||||
const response = await fetch(`${baseUrl}/functions/v1/handle-key-rotation`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.access_token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
console.error('[E2EE] Key rotation failed:', text);
|
||||
return { success: false, updated: 0, error: text || 'Failed to rotate keys' };
|
||||
}
|
||||
|
||||
const result = (await response.json()) as KeyRotationResult;
|
||||
console.log(`[E2EE] Key rotation complete: ${result.updated} messages updated`);
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.error('[E2EE] Key rotation error:', err);
|
||||
return {
|
||||
success: false,
|
||||
updated: 0,
|
||||
error: err instanceof Error ? err.message : 'Unknown error',
|
||||
};
|
||||
} finally {
|
||||
inFlight = null;
|
||||
}
|
||||
})();
|
||||
|
||||
return inFlight;
|
||||
}
|
||||
|
||||
export function triggerAutoKeyRotation(): Promise<KeyRotationResult | null> | null {
|
||||
const now = Date.now();
|
||||
|
||||
if (autoRotationInFlight) return autoRotationInFlight;
|
||||
if (now - lastAutoRotationAt < AUTO_ROTATION_MIN_INTERVAL_MS) {
|
||||
return null;
|
||||
}
|
||||
|
||||
lastAutoRotationAt = now;
|
||||
|
||||
autoRotationInFlight = (async () => {
|
||||
console.log('[E2EE] Auto key rotation triggered by decryption failure.');
|
||||
const result = await triggerKeyRotation();
|
||||
await reencryptPendingMessages({ force: true });
|
||||
return result;
|
||||
})().finally(() => {
|
||||
autoRotationInFlight = null;
|
||||
});
|
||||
|
||||
return autoRotationInFlight;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import type { Ionicons } from "@expo/vector-icons";
|
||||
|
||||
type IoniconsName = keyof typeof Ionicons.glyphMap;
|
||||
|
||||
/**
|
||||
* Maps emoji icons to Ionicons for a more professional, consistent appearance.
|
||||
* This eliminates the "AI slop" feel of heavy emoji usage while maintaining
|
||||
* visual differentiation between packs.
|
||||
*/
|
||||
export const PACK_ICON_MAP: Record<string, IoniconsName> = {
|
||||
// Career & Professional
|
||||
"💼": "briefcase-outline",
|
||||
|
||||
// Relationships & Connection
|
||||
"🔗": "heart-half-outline",
|
||||
"❤️": "heart-outline",
|
||||
"💫": "sparkles-outline",
|
||||
|
||||
// Secrets & Mystery
|
||||
"🤫": "eye-off-outline",
|
||||
"🗝️": "key-outline",
|
||||
|
||||
// Romance & Dates
|
||||
"🌹": "flower-outline",
|
||||
"🍷": "wine-outline",
|
||||
"✨": "sparkles-outline",
|
||||
|
||||
// Adventure & Travel
|
||||
"✈️": "airplane-outline",
|
||||
"🚗": "car-outline",
|
||||
"🏡": "home-outline",
|
||||
|
||||
// Playful & Games
|
||||
"🎭": "color-wand-outline",
|
||||
"🎲": "dice-outline",
|
||||
"🎁": "gift-outline",
|
||||
|
||||
// Intimacy & Physical
|
||||
"🔄": "sync-outline",
|
||||
"😈": "flash-outline",
|
||||
"🔥": "flame-outline",
|
||||
|
||||
// Goals & Planning
|
||||
"🎯": "flag-outline",
|
||||
"☀️": "sunny-outline",
|
||||
|
||||
// Default fallbacks
|
||||
"📦": "cube-outline",
|
||||
"📁": "folder-outline",
|
||||
};
|
||||
|
||||
export const DEFAULT_PACK_ICON: IoniconsName = "layers-outline";
|
||||
|
||||
/**
|
||||
* Converts an emoji icon string to an Ionicons name.
|
||||
* Falls back to DEFAULT_PACK_ICON if no mapping exists.
|
||||
*/
|
||||
export function getPackIconName(emoji: string | null | undefined): IoniconsName {
|
||||
if (!emoji) return DEFAULT_PACK_ICON;
|
||||
return PACK_ICON_MAP[emoji] || DEFAULT_PACK_ICON;
|
||||
}
|
||||
Reference in new issue
Block a user