Fix EAS local builds and resolve require cycles

- Add dotenv-cli for EAS local builds to load env vars
- Break circular dependencies in store/hooks imports
- Add env var validation with clear error messages
- Remove hardcoded Sentry auth token from sentry.properties
- Add .ipa files to .gitignore

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jjbruntonandClaude Opus 4.5 committed 2026-01-04 16:43:24 +00:00
1 parent bb2ce5a6a6
commit 58209b94ef
19 files changed
+458 -114

No files matched your search

+4 -12
View File
@@ -14,6 +14,8 @@ import { useEncryptionKeys } from './useEncryptionKeys';
import { getCachedSignedUrl, getStoragePath } from '../lib/imageCache';
import { supabase } from '../lib/supabase';
import { reencryptPendingMessages } from '../lib/encryption/reencryptPendingMessages';
// Import shared cache from mediaCache to avoid circular dependency
import { decryptedMediaCache, clearDecryptedMediaCache } from '../lib/mediaCache';
export type DecryptedMediaErrorCode =
| 'E2EE_NOT_SUPPORTED'
@@ -22,8 +24,8 @@ export type DecryptedMediaErrorCode =
| 'E2EE_PENDING_RECIPIENT_KEY'
| 'E2EE_DECRYPT_FAILED';
// In-memory cache to avoid re-decrypting the same media
const decryptedMediaCache = new Map<string, string>();
// Re-export clearDecryptedMediaCache for backwards compatibility
export { clearDecryptedMediaCache };
interface DecryptedMediaState {
/** Local file URI to the decrypted media (or null if not ready) */
@@ -396,13 +398,3 @@ export function useDecryptedMedia({
};
}
/**
* Clear the decrypted media cache
* Call this when logging out or when memory is low
*/
export async function clearDecryptedMediaCache(): Promise<void> {
for (const uri of decryptedMediaCache.values()) {
await cleanupDecryptedMedia(uri);
}
decryptedMediaCache.clear();
}
+2 -1
View File
@@ -22,7 +22,8 @@ import {
} from '../lib/encryption';
import type { RSAPublicKeyJWK } from '../lib/encryption';
import { supabase } from '../lib/supabase';
import { useAuthStore } from '../store';
// Import directly from authStore to avoid circular dependency with store/index.ts
import { useAuthStore } from '../store/authStore';
interface UseEncryptionKeysReturn {
/** RSA private key in JWK format */
+2 -1
View File
@@ -1,6 +1,7 @@
import { router } from "expo-router";
import { supabase } from "./supabase";
import { useAuthStore } from "../store";
// Import directly from authStore to avoid circular dependency with store/index.ts
import { useAuthStore } from "../store/authStore";
/**
* Handles authentication errors from edge function calls.
+22
View File
@@ -0,0 +1,22 @@
/**
* Media Cache Utilities
*
* Extracted from useDecryptedMedia to avoid circular dependencies.
* This module handles caching of decrypted media files.
*/
import { cleanupDecryptedMedia } from './encryption';
// In-memory cache to avoid re-decrypting the same media
export const decryptedMediaCache = new Map<string, string>();
/**
* Clears all cached decrypted media files from disk and memory.
* Call this on sign out to free up storage.
*/
export async function clearDecryptedMediaCache(): Promise<void> {
for (const uri of decryptedMediaCache.values()) {
await cleanupDecryptedMedia(uri);
}
decryptedMediaCache.clear();
}
+19 -3
View File
@@ -47,10 +47,26 @@ const ExpoStorageAdapter = {
},
};
const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL!;
const supabaseAnonKey = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY!;
const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL;
const supabaseAnonKey = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY;
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
// Validate environment variables at startup (skip in test environment where mocks handle this)
const isTestEnv = process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined;
if (!isTestEnv && (!supabaseUrl || !supabaseAnonKey)) {
const missing = [];
if (!supabaseUrl) missing.push('EXPO_PUBLIC_SUPABASE_URL');
if (!supabaseAnonKey) missing.push('EXPO_PUBLIC_SUPABASE_ANON_KEY');
const errorMsg = `Missing required environment variables: ${missing.join(', ')}. App cannot start.`;
console.error('[Supabase]', errorMsg);
// In production, this will be caught by Sentry before crashing
throw new Error(errorMsg);
}
// Use dummy values in test environment (supabase-js is mocked anyway)
const finalUrl = supabaseUrl || (isTestEnv ? 'https://test.supabase.co' : '');
const finalKey = supabaseAnonKey || (isTestEnv ? 'test-key' : '');
export const supabase = createClient(finalUrl, finalKey, {
auth: {
storage: ExpoStorageAdapter,
autoRefreshToken: true,
+5 -5
View File
@@ -1,9 +1,9 @@
/**
* Crypto polyfill for React Native
*
*
* This file must be imported BEFORE any code that uses crypto.
* It installs react-native-quick-crypto as the global crypto implementation.
*
*
* On web, we skip this since the native Web Crypto API is available.
*/
@@ -13,17 +13,17 @@ if (Platform.OS !== 'web') {
try {
// Import react-native-quick-crypto to install its polyfill
const QuickCrypto = require('react-native-quick-crypto');
// Install the polyfill globally if not already present
if (typeof global.crypto === 'undefined') {
(global as any).crypto = QuickCrypto;
}
// Also ensure subtle is available
if (global.crypto && !global.crypto.subtle) {
(global.crypto as any).subtle = QuickCrypto.subtle;
}
console.log('[Crypto] react-native-quick-crypto polyfill installed');
} catch (error) {
console.error('[Crypto] Failed to install crypto polyfill:', error);
+1 -1
View File
@@ -2,7 +2,7 @@ import { create } from "zustand";
import { supabase } from "../lib/supabase";
import { Events } from "../lib/analytics";
import { clearKeys } from "../lib/encryption";
import { clearDecryptedMediaCache } from "../hooks/useDecryptedMedia";
import { clearDecryptedMediaCache } from "../lib/mediaCache";
import type { Profile, Couple } from "@/types";
import type { RSAPublicKeyJWK, RSAPrivateKeyJWK } from "../lib/encryption/types";
+2 -12
View File
@@ -1,20 +1,10 @@
/// <reference types="jest" />
// Environment variables are set in setupEnv.js (runs before modules load)
// Some modules rely on this global existing in RN.
(global as any).__DEV__ = true;
if (!process.env.EXPO_PUBLIC_SUPABASE_URL) {
process.env.EXPO_PUBLIC_SUPABASE_URL = 'https://example.supabase.co';
}
if (!process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY) {
process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY = 'test-anon-key';
}
if (!process.env.EXPO_PUBLIC_REVENUECAT_ENTITLEMENT_ID) {
process.env.EXPO_PUBLIC_REVENUECAT_ENTITLEMENT_ID = 'pro';
}
jest.mock('@supabase/supabase-js', () => {
const makeChannel = () => {
const channel: any = {
+7
View File
@@ -0,0 +1,7 @@
// Set test environment variables BEFORE modules are loaded
// This must run in setupFiles (not setupFilesAfterEnv) to ensure
// env vars are available when supabase.ts is imported
process.env.EXPO_PUBLIC_SUPABASE_URL = 'https://example.supabase.co';
process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY = 'test-anon-key';
process.env.EXPO_PUBLIC_REVENUECAT_ENTITLEMENT_ID = 'pro';