mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-11-02 02:19:43 +00:00
Update
This commit is contained in:
1 parent
8acc43981e
commit
118dadc69a
122 files changed
+1688
-218
No files matched your search
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user