This commit is contained in:
Jamie Brunton committed 2025-12-29 22:15:41 +00:00
1 parent 8acc43981e
commit 118dadc69a
122 files changed
+1688 -218

No files matched your search

+112
View File
@@ -0,0 +1,112 @@
import * as LocalAuthentication from "expo-local-authentication";
import * as SecureStore from "expo-secure-store";
import { Platform } from "react-native";
const BIOMETRIC_ENABLED_KEY = "biometric_auth_enabled";
/**
* Check if biometric authentication (Face ID/Touch ID) is available on the device
*/
export async function isBiometricAvailable(): Promise<boolean> {
if (Platform.OS === "web") {
return false;
}
try {
const compatible = await LocalAuthentication.hasHardwareAsync();
if (!compatible) return false;
const enrolled = await LocalAuthentication.isEnrolledAsync();
return enrolled;
} catch (error) {
console.error("Error checking biometric availability:", error);
return false;
}
}
/**
* Get the type of biometric authentication available (Face ID, Touch ID, etc.)
*/
export async function getBiometricType(): Promise<string> {
if (Platform.OS === "web") {
return "None";
}
try {
const types = await LocalAuthentication.supportedAuthenticationTypesAsync();
if (types.includes(LocalAuthentication.AuthenticationType.FACIAL_RECOGNITION)) {
return Platform.OS === "ios" ? "Face ID" : "Face Recognition";
}
if (types.includes(LocalAuthentication.AuthenticationType.FINGERPRINT)) {
return Platform.OS === "ios" ? "Touch ID" : "Fingerprint";
}
if (types.includes(LocalAuthentication.AuthenticationType.IRIS)) {
return "Iris";
}
return "Biometric";
} catch (error) {
console.error("Error getting biometric type:", error);
return "Biometric";
}
}
/**
* Check if biometric authentication is enabled by the user
*/
export async function isBiometricEnabled(): Promise<boolean> {
if (Platform.OS === "web") {
return false;
}
try {
const value = await SecureStore.getItemAsync(BIOMETRIC_ENABLED_KEY);
return value === "true";
} catch (error) {
console.error("Error reading biometric setting:", error);
return false;
}
}
/**
* Enable or disable biometric authentication
*/
export async function setBiometricEnabled(enabled: boolean): Promise<void> {
if (Platform.OS === "web") {
return;
}
try {
await SecureStore.setItemAsync(BIOMETRIC_ENABLED_KEY, enabled ? "true" : "false");
} catch (error) {
console.error("Error saving biometric setting:", error);
throw error;
}
}
/**
* Authenticate the user using biometrics
* @returns true if authentication was successful, false otherwise
*/
export async function authenticateWithBiometric(): Promise<boolean> {
if (Platform.OS === "web") {
return true;
}
try {
const biometricType = await getBiometricType();
const result = await LocalAuthentication.authenticateAsync({
promptMessage: `Unlock Sauci with ${biometricType}`,
cancelLabel: "Cancel",
disableDeviceFallback: false, // Allow passcode fallback
fallbackLabel: "Use Passcode",
});
return result.success;
} catch (error) {
console.error("Error during biometric authentication:", error);
return false;
}
}
+12 -10
View File
@@ -1,14 +1,16 @@
import "react-native-url-polyfill/auto";
import { createClient } from "@supabase/supabase-js";
import * as SecureStore from "expo-secure-store";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { Platform } from "react-native";
// In-memory storage fallback for SSR/Node.js environments
const memoryStorage: Record<string, string> = {};
// SecureStore adapter for Supabase auth
const ExpoSecureStoreAdapter = {
getItem: (key: string) => {
// AsyncStorage adapter for Supabase auth
// Note: We use AsyncStorage instead of SecureStore because Supabase JWTs
// can exceed SecureStore's 2048 byte limit
const ExpoStorageAdapter = {
getItem: async (key: string): Promise<string | null> => {
if (Platform.OS === "web") {
// Check if we're in a browser environment with localStorage
if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") {
@@ -17,9 +19,9 @@ const ExpoSecureStoreAdapter = {
// Fallback for SSR/Node.js
return memoryStorage[key] ?? null;
}
return SecureStore.getItemAsync(key);
return AsyncStorage.getItem(key);
},
setItem: (key: string, value: string) => {
setItem: async (key: string, value: string): Promise<void> => {
if (Platform.OS === "web") {
if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") {
window.localStorage.setItem(key, value);
@@ -29,9 +31,9 @@ const ExpoSecureStoreAdapter = {
memoryStorage[key] = value;
return;
}
return SecureStore.setItemAsync(key, value);
await AsyncStorage.setItem(key, value);
},
removeItem: (key: string) => {
removeItem: async (key: string): Promise<void> => {
if (Platform.OS === "web") {
if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") {
window.localStorage.removeItem(key);
@@ -41,7 +43,7 @@ const ExpoSecureStoreAdapter = {
delete memoryStorage[key];
return;
}
return SecureStore.deleteItemAsync(key);
await AsyncStorage.removeItem(key);
},
};
@@ -53,7 +55,7 @@ console.log("DEBUG - Anon key preview:", supabaseAnonKey?.substring(0, 30) + "..
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
auth: {
storage: ExpoSecureStoreAdapter,
storage: ExpoStorageAdapter,
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: Platform.OS === "web",
+51
View File
@@ -0,0 +1,51 @@
import * as SecureStore from "expo-secure-store";
import { Platform } from "react-native";
const STORAGE_KEY = "swipe_tutorial_seen";
async function getStorage(): Promise<boolean> {
try {
let data: string | null = null;
if (Platform.OS === "web") {
if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") {
data = window.localStorage.getItem(STORAGE_KEY);
}
} else {
data = await SecureStore.getItemAsync(STORAGE_KEY);
}
return data === "true";
} catch (error) {
console.error("Error reading swipe tutorial state:", error);
}
return false;
}
async function setStorage(seen: boolean): Promise<void> {
try {
const data = seen ? "true" : "false";
if (Platform.OS === "web") {
if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") {
window.localStorage.setItem(STORAGE_KEY, data);
}
} else {
await SecureStore.setItemAsync(STORAGE_KEY, data);
}
} catch (error) {
console.error("Error saving swipe tutorial state:", error);
}
}
export async function hasSeenSwipeTutorial(): Promise<boolean> {
return getStorage();
}
export async function markSwipeTutorialSeen(): Promise<void> {
await setStorage(true);
}
export async function resetSwipeTutorial(): Promise<void> {
await setStorage(false);
}