@digilogiclabs/saas-factory-auth
Version:
Modern authentication package for Next.js 15+ and React 18.2+/19+ applications with React Native 0.72+ support using Supabase and Firebase
1,493 lines (1,483 loc) • 51.9 kB
JavaScript
'use strict';
var react = require('react');
var supabaseJs = require('@supabase/supabase-js');
var app = require('firebase/app');
var auth = require('firebase/auth');
var zustand = require('zustand');
var middleware = require('zustand/middleware');
var immer = require('zustand/middleware/immer');
var jsxRuntime = require('react/jsx-runtime');
var ssr = require('@supabase/ssr');
var server_js = require('next/server.js');
require('next/headers.js');
// src/components/AuthProvider.tsx
// src/core/IAuthProvider.ts
var AuthError = class _AuthError extends Error {
constructor(type, message, originalError, context) {
super(message);
this.type = type;
this.originalError = originalError;
this.context = context;
this.name = "AuthError";
this.timestamp = /* @__PURE__ */ new Date();
this.code = type;
Object.setPrototypeOf(this, _AuthError.prototype);
if (Error.captureStackTrace) {
Error.captureStackTrace(this, _AuthError);
}
}
timestamp;
code;
/**
* Convert error to JSON for logging/debugging
*/
toJSON() {
const originalErrorDetails = this.originalError instanceof Error ? {
name: this.originalError.name,
message: this.originalError.message,
stack: this.originalError.stack
} : this.originalError ? { message: String(this.originalError) } : void 0;
return {
name: this.name,
type: this.type,
code: this.code,
message: this.message,
timestamp: this.timestamp.toISOString(),
context: this.context,
originalError: originalErrorDetails
};
}
/**
* Check if error is of a specific type
*/
isType(type) {
return this.type === type;
}
/**
* Check if error is retryable
*/
isRetryable() {
return [
"NETWORK_ERROR" /* NETWORK_ERROR */,
"PROVIDER_ERROR" /* PROVIDER_ERROR */
].includes(this.type);
}
};
// src/config/env.ts
var getAuthConfig = () => {
const config = {};
if (typeof process !== "undefined" && process.env) {
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY;
const firebaseApiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY || process.env.FIREBASE_API_KEY;
const firebaseAuthDomain = process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN || process.env.FIREBASE_AUTH_DOMAIN;
const firebaseProjectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID || process.env.FIREBASE_PROJECT_ID;
if (supabaseUrl) config.supabaseUrl = supabaseUrl;
if (supabaseAnonKey) config.supabaseAnonKey = supabaseAnonKey;
if (firebaseApiKey) config.firebaseApiKey = firebaseApiKey;
if (firebaseAuthDomain) config.firebaseAuthDomain = firebaseAuthDomain;
if (firebaseProjectId) config.firebaseProjectId = firebaseProjectId;
return config;
}
try {
const Constants = new Function("return require('expo-constants').default")();
const expoConfig = Constants.expoConfig?.extra || {};
if (expoConfig.supabaseUrl) config.supabaseUrl = expoConfig.supabaseUrl;
if (expoConfig.supabaseAnonKey) config.supabaseAnonKey = expoConfig.supabaseAnonKey;
if (expoConfig.firebaseApiKey) config.firebaseApiKey = expoConfig.firebaseApiKey;
if (expoConfig.firebaseAuthDomain) config.firebaseAuthDomain = expoConfig.firebaseAuthDomain;
if (expoConfig.firebaseProjectId) config.firebaseProjectId = expoConfig.firebaseProjectId;
return config;
} catch {
}
try {
const RNConfig = new Function("return require('react-native-config').default")();
if (RNConfig.SUPABASE_URL) config.supabaseUrl = RNConfig.SUPABASE_URL;
if (RNConfig.SUPABASE_ANON_KEY) config.supabaseAnonKey = RNConfig.SUPABASE_ANON_KEY;
if (RNConfig.FIREBASE_API_KEY) config.firebaseApiKey = RNConfig.FIREBASE_API_KEY;
if (RNConfig.FIREBASE_AUTH_DOMAIN) config.firebaseAuthDomain = RNConfig.FIREBASE_AUTH_DOMAIN;
if (RNConfig.FIREBASE_PROJECT_ID) config.firebaseProjectId = RNConfig.FIREBASE_PROJECT_ID;
return config;
} catch {
}
return {};
};
var validateConfig = (config, provider) => {
if (provider === "supabase") {
if (!config.supabaseUrl || !config.supabaseAnonKey) {
throw new Error(
"Supabase configuration is incomplete. Please ensure NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY are set."
);
}
}
if (provider === "firebase") {
if (!config.firebaseApiKey || !config.firebaseAuthDomain || !config.firebaseProjectId) {
throw new Error(
"Firebase configuration is incomplete. Please ensure NEXT_PUBLIC_FIREBASE_API_KEY, NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, and NEXT_PUBLIC_FIREBASE_PROJECT_ID are set."
);
}
}
};
// src/types.ts
var AuthEvent = /* @__PURE__ */ ((AuthEvent2) => {
AuthEvent2["SIGNED_IN"] = "SIGNED_IN";
AuthEvent2["SIGNED_OUT"] = "SIGNED_OUT";
AuthEvent2["TOKEN_REFRESHED"] = "TOKEN_REFRESHED";
AuthEvent2["USER_UPDATED"] = "USER_UPDATED";
return AuthEvent2;
})(AuthEvent || {});
// src/core/SupabaseAuthProvider.ts
var mapSupabaseUserToUser = (supabaseUser) => {
if (!supabaseUser) return null;
const metadata = supabaseUser.user_metadata || {};
return {
id: supabaseUser.id,
email: supabaseUser.email,
emailVerified: supabaseUser.email_confirmed_at ? true : false,
// Name fields with fallback logic
name: metadata.name || metadata.full_name || metadata.display_name || null,
firstName: metadata.firstName || metadata.first_name || null,
lastName: metadata.lastName || metadata.last_name || null,
fullName: metadata.fullName || metadata.full_name || metadata.name || null,
// Profile fields
avatar: metadata.avatar || metadata.avatar_url || metadata.picture || null,
phone: metadata.phone || metadata.phone_number || null,
website: metadata.website || null,
bio: metadata.bio || null,
// Additional metadata
locale: metadata.locale || null,
timezone: metadata.timezone || null,
dateOfBirth: metadata.dateOfBirth || metadata.date_of_birth || null,
// Timestamps
createdAt: supabaseUser.created_at,
updatedAt: supabaseUser.updated_at,
lastSignInAt: supabaseUser.last_sign_in_at,
// OAuth provider data
providerData: supabaseUser.identities?.reduce((acc, identity) => {
if (identity.provider && identity.identity_data) {
acc[identity.provider] = {
id: identity.id,
username: identity.identity_data.user_name || identity.identity_data.login || null,
raw: identity.identity_data
};
}
return acc;
}, {}) || {},
// Include all other metadata for backward compatibility
...metadata
};
};
var mapSupabaseError = (error) => {
if (error instanceof AuthError) {
return error;
}
if (typeof error !== "object" || error === null || !("message" in error)) {
return new AuthError("UNKNOWN" /* UNKNOWN */, "An unknown error occurred", error);
}
const err = error;
const message = err.message || "Unknown error occurred";
const code = err.code;
if (code === "ERR_NAME_NOT_RESOLVED" || message.includes("ERR_NAME_NOT_RESOLVED")) {
return new AuthError(
"CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
"Invalid Supabase URL. Please check your NEXT_PUBLIC_SUPABASE_URL configuration.",
err
);
}
if (message.includes("Failed to fetch") || message.includes("NetworkError")) {
return new AuthError(
"NETWORK_ERROR" /* NETWORK_ERROR */,
"Network error. Please check your internet connection and Supabase URL.",
err
);
}
if (message.includes("Invalid login credentials")) {
return new AuthError("INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */, message, err);
}
if (message.includes("User not found")) {
return new AuthError("USER_NOT_FOUND" /* USER_NOT_FOUND */, message, err);
}
if (message.includes("User already registered")) {
return new AuthError("EMAIL_ALREADY_EXISTS" /* EMAIL_ALREADY_EXISTS */, message, err);
}
if (message.includes("Password should be")) {
return new AuthError("WEAK_PASSWORD" /* WEAK_PASSWORD */, message, err);
}
if (message.includes("network") || message.includes("fetch")) {
return new AuthError("NETWORK_ERROR" /* NETWORK_ERROR */, message, err);
}
return new AuthError("PROVIDER_ERROR" /* PROVIDER_ERROR */, message, err);
};
var validateEmail = (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
throw new AuthError("VALIDATION_ERROR" /* VALIDATION_ERROR */, "Invalid email format");
}
};
var validateSupabaseUrl = (url) => {
if (!url || !url.startsWith("http")) {
throw new AuthError(
"CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
"Supabase URL must start with http:// or https://"
);
}
const urlObj = new URL(url);
if (!urlObj.hostname.endsWith(".supabase.co") && !urlObj.hostname.includes("localhost")) {
console.warn("Warning: URL does not appear to be a valid Supabase URL");
}
if (url.includes("supabase.co/") && !url.includes(".supabase.co")) {
throw new AuthError(
"CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
"Invalid Supabase URL format. Should be: https://[project-ref].supabase.co"
);
}
};
var SupabaseAuthProvider = class {
client;
storage;
constructor(storage) {
this.storage = storage;
const config = getAuthConfig();
validateConfig(config, "supabase");
try {
validateSupabaseUrl(config.supabaseUrl);
} catch (error) {
console.error("Supabase URL validation failed:", error);
throw error;
}
try {
this.client = supabaseJs.createClient(config.supabaseUrl, config.supabaseAnonKey, {
auth: {
storage: {
getItem: (key) => this.storage.getItem(key),
setItem: (key, value) => this.storage.setItem(key, value),
removeItem: (key) => this.storage.removeItem(key)
},
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: true
}
});
} catch (error) {
console.error("Failed to create Supabase client:", error);
throw new AuthError(
"CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
"Failed to initialize Supabase client. Please check your configuration.",
error
);
}
}
/**
* Validates Supabase configuration
*/
async validateConfiguration() {
try {
const config = getAuthConfig();
validateConfig(config, "supabase");
validateSupabaseUrl(config.supabaseUrl);
const { error } = await this.client.auth.getSession();
if (error && error.message.includes("ERR_NAME_NOT_RESOLVED")) {
throw new AuthError(
"CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
"Cannot connect to Supabase. Please verify your NEXT_PUBLIC_SUPABASE_URL is correct.",
error
);
}
} catch (error) {
if (error instanceof AuthError) {
throw error;
}
throw new AuthError(
"CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
error instanceof Error ? error.message : "Invalid Supabase configuration"
);
}
}
async signIn(email, password) {
try {
validateEmail(email);
const { data, error } = await this.client.auth.signInWithPassword({ email, password });
if (error) {
return { user: null, session: null, error: mapSupabaseError(error) };
}
const user = mapSupabaseUserToUser(data.user);
const session = data.session ? {
user,
access_token: data.session.access_token,
refresh_token: data.session.refresh_token,
expires_at: data.session.expires_at
} : null;
return { user, session, error: null };
} catch (error) {
return {
user: null,
session: null,
error: mapSupabaseError(error)
};
}
}
async signUp(email, password) {
try {
validateEmail(email);
const { data, error } = await this.client.auth.signUp({ email, password });
if (error) {
return { user: null, session: null, error: mapSupabaseError(error) };
}
const user = mapSupabaseUserToUser(data.user);
const session = data.session ? {
user,
access_token: data.session.access_token,
refresh_token: data.session.refresh_token,
expires_at: data.session.expires_at
} : null;
return { user, session, error: null };
} catch (error) {
return { user: null, session: null, error: mapSupabaseError(error) };
}
}
async signInWithOAuth(provider, redirectTo) {
try {
const options = {};
if (redirectTo) {
options.redirectTo = redirectTo;
}
let supabaseProvider = provider;
switch (provider) {
case "microsoft":
supabaseProvider = "azure";
break;
case "apple":
case "google":
case "github":
case "facebook":
case "twitter":
case "discord":
case "linkedin":
case "spotify":
case "twitch":
case "slack":
case "notion":
supabaseProvider = provider;
break;
default:
throw new AuthError(
"PROVIDER_ERROR" /* PROVIDER_ERROR */,
`OAuth provider '${provider}' is not supported by Supabase`
);
}
const { error } = await this.client.auth.signInWithOAuth({
provider: supabaseProvider,
// Type assertion needed due to Supabase's strict typing
options
});
if (error) {
throw mapSupabaseError(error);
}
} catch (error) {
throw mapSupabaseError(error);
}
}
async signInWithMagicLink(email, redirectTo) {
try {
validateEmail(email);
const options = {};
if (redirectTo) {
options.emailRedirectTo = redirectTo;
}
const { error } = await this.client.auth.signInWithOtp({
email,
options
});
if (error) {
throw mapSupabaseError(error);
}
} catch (error) {
throw mapSupabaseError(error);
}
}
async signOut() {
try {
const { error } = await this.client.auth.signOut();
if (error) {
return { error: mapSupabaseError(error) };
}
return { error: null };
} catch (error) {
return { error: mapSupabaseError(error) };
}
}
async getUser() {
try {
const { data: { user }, error } = await this.client.auth.getUser();
if (error) {
throw mapSupabaseError(error);
}
return mapSupabaseUserToUser(user);
} catch (error) {
throw mapSupabaseError(error);
}
}
async getSession() {
try {
const { data: { session }, error } = await this.client.auth.getSession();
if (error) {
if (error.message === "Auth session missing!") {
return null;
}
throw mapSupabaseError(error);
}
if (!session) return null;
const user = mapSupabaseUserToUser(session.user);
return {
user,
access_token: session.access_token,
refresh_token: session.refresh_token,
expires_at: session.expires_at
};
} catch (error) {
if (error instanceof AuthError) {
throw error;
}
throw new AuthError("PROVIDER_ERROR" /* PROVIDER_ERROR */, "Get session failed", error);
}
}
onAuthStateChange(callback) {
const { data: { subscription } } = this.client.auth.onAuthStateChange((event, session) => {
let authEvent;
switch (event) {
case "SIGNED_IN":
authEvent = "SIGNED_IN" /* SIGNED_IN */;
break;
case "SIGNED_OUT":
authEvent = "SIGNED_OUT" /* SIGNED_OUT */;
break;
case "TOKEN_REFRESHED":
authEvent = "TOKEN_REFRESHED" /* TOKEN_REFRESHED */;
break;
case "USER_UPDATED":
authEvent = "USER_UPDATED" /* USER_UPDATED */;
break;
case "INITIAL_SESSION":
authEvent = session ? "SIGNED_IN" /* SIGNED_IN */ : "SIGNED_OUT" /* SIGNED_OUT */;
break;
case "MFA_CHALLENGE_VERIFIED":
authEvent = "USER_UPDATED" /* USER_UPDATED */;
break;
case "PASSWORD_RECOVERY":
authEvent = "USER_UPDATED" /* USER_UPDATED */;
break;
default:
console.warn(`Unhandled Supabase auth event: ${event}`);
authEvent = "USER_UPDATED" /* USER_UPDATED */;
}
const user = mapSupabaseUserToUser(session?.user || null);
const authSession = user && session ? {
user,
access_token: session.access_token,
refresh_token: session.refresh_token,
expires_at: session.expires_at
} : null;
callback(authEvent, authSession);
});
return { unsubscribe: () => subscription.unsubscribe() };
}
async resetPassword(email) {
try {
validateEmail(email);
const { error } = await this.client.auth.resetPasswordForEmail(email);
if (error) {
return { error: mapSupabaseError(error) };
}
return { error: null };
} catch (error) {
return { error: mapSupabaseError(error) };
}
}
async updateProfile(updates) {
try {
const { data: { session }, error: sessionError } = await this.client.auth.getSession();
if (sessionError || !session?.user) {
return {
user: null,
error: new AuthError(
"INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */,
"User must be authenticated to update profile"
)
};
}
const userMetadata = { ...session.user.user_metadata };
if (updates.name !== void 0) {
userMetadata.name = updates.name;
userMetadata.display_name = updates.name;
}
if (updates.firstName !== void 0) {
userMetadata.firstName = updates.firstName;
userMetadata.first_name = updates.firstName;
}
if (updates.lastName !== void 0) {
userMetadata.lastName = updates.lastName;
userMetadata.last_name = updates.lastName;
}
if (updates.fullName !== void 0) {
userMetadata.fullName = updates.fullName;
userMetadata.full_name = updates.fullName;
userMetadata.name = updates.fullName;
userMetadata.display_name = updates.fullName;
}
if ((updates.firstName !== void 0 || updates.lastName !== void 0) && updates.fullName === void 0) {
const firstName = updates.firstName || userMetadata.firstName || userMetadata.first_name || "";
const lastName = updates.lastName || userMetadata.lastName || userMetadata.last_name || "";
const constructedFullName = `${firstName} ${lastName}`.trim();
if (constructedFullName) {
userMetadata.full_name = constructedFullName;
userMetadata.fullName = constructedFullName;
userMetadata.name = constructedFullName;
}
}
if (updates.avatar !== void 0) {
userMetadata.avatar = updates.avatar;
userMetadata.avatar_url = updates.avatar;
userMetadata.picture = updates.avatar;
}
if (updates.phone !== void 0) {
userMetadata.phone = updates.phone;
userMetadata.phone_number = updates.phone;
}
if (updates.website !== void 0) {
userMetadata.website = updates.website;
}
if (updates.bio !== void 0) {
userMetadata.bio = updates.bio;
}
const knownFields = ["name", "firstName", "lastName", "fullName", "avatar", "phone", "website", "bio"];
Object.keys(updates).forEach((key) => {
if (!knownFields.includes(key)) {
userMetadata[key] = updates[key];
}
});
const { data, error } = await this.client.auth.updateUser({
data: userMetadata
});
if (error) {
return { user: null, error: mapSupabaseError(error) };
}
const updatedUser = mapSupabaseUserToUser(data.user);
return { user: updatedUser, error: null };
} catch (error) {
return { user: null, error: mapSupabaseError(error) };
}
}
};
var mapFirebaseUserToUser = (firebaseUser) => {
if (!firebaseUser) return null;
return {
id: firebaseUser.uid,
email: firebaseUser.email || void 0,
name: firebaseUser.displayName,
avatar: firebaseUser.photoURL,
// Add other Firebase-specific metadata if needed
phoneNumber: firebaseUser.phoneNumber,
emailVerified: firebaseUser.emailVerified,
isAnonymous: firebaseUser.isAnonymous,
tenantId: firebaseUser.tenantId,
providerData: firebaseUser.providerData,
creationTime: firebaseUser.metadata?.creationTime,
lastSignInTime: firebaseUser.metadata?.lastSignInTime
};
};
var getFirebaseAccessToken = async (user) => {
try {
return await user.getIdToken();
} catch (error) {
console.warn("Failed to get Firebase ID token:", error);
return "";
}
};
var mapFirebaseError = (error) => {
const code = error.code || "";
const message = error.message || "Unknown error occurred";
switch (code) {
case "auth/invalid-email":
case "auth/user-disabled":
case "auth/user-not-found":
case "auth/wrong-password":
case "auth/invalid-credential":
return new AuthError("INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */, message, error);
case "auth/email-already-in-use":
return new AuthError("EMAIL_ALREADY_EXISTS" /* EMAIL_ALREADY_EXISTS */, message, error);
case "auth/weak-password":
return new AuthError("WEAK_PASSWORD" /* WEAK_PASSWORD */, message, error);
case "auth/network-request-failed":
case "auth/timeout":
return new AuthError("NETWORK_ERROR" /* NETWORK_ERROR */, message, error);
case "auth/configuration-not-found":
case "auth/invalid-api-key":
return new AuthError("CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */, message, error);
default:
return new AuthError("PROVIDER_ERROR" /* PROVIDER_ERROR */, message, error);
}
};
var validateEmail2 = (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
throw new AuthError("VALIDATION_ERROR" /* VALIDATION_ERROR */, "Invalid email format");
}
};
var getOAuthProvider = (provider) => {
switch (provider) {
case "google":
return new auth.GoogleAuthProvider();
case "github":
return new auth.GithubAuthProvider();
case "facebook":
return new auth.FacebookAuthProvider();
case "twitter":
return new auth.TwitterAuthProvider();
case "apple": {
const appleProvider = new auth.OAuthProvider("apple.com");
return appleProvider;
}
case "discord": {
const discordProvider = new auth.OAuthProvider("discord.com");
return discordProvider;
}
default:
throw new AuthError(
"VALIDATION_ERROR" /* VALIDATION_ERROR */,
`Unsupported OAuth provider for Firebase: ${provider}`
);
}
};
var FirebaseAuthProvider = class {
app;
auth;
storage;
constructor(storage) {
this.storage = storage;
const config = getAuthConfig();
validateConfig(config, "firebase");
this.app = app.initializeApp({
apiKey: config.firebaseApiKey,
authDomain: config.firebaseAuthDomain,
projectId: config.firebaseProjectId
});
this.auth = auth.getAuth(this.app);
auth.setPersistence(this.auth, this.storage || auth.browserLocalPersistence).catch((error) => {
console.error("Failed to set Firebase auth persistence:", error);
});
}
/**
* Validates Firebase configuration
*/
async validateConfiguration() {
try {
const config = getAuthConfig();
validateConfig(config, "firebase");
} catch (error) {
throw new AuthError(
"CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
error instanceof Error ? error.message : "Invalid Firebase configuration"
);
}
}
async signIn(email, password) {
try {
validateEmail2(email);
const userCredential = await auth.signInWithEmailAndPassword(this.auth, email, password);
const user = mapFirebaseUserToUser(userCredential.user);
const accessToken = await getFirebaseAccessToken(userCredential.user);
const session = user ? {
user,
access_token: accessToken,
refresh_token: void 0,
// Firebase doesn't expose refresh token directly
expires_at: void 0
// Firebase handles token expiration internally
} : null;
return { user, session, error: null };
} catch (error) {
return { user: null, session: null, error: mapFirebaseError(error) };
}
}
async signUp(email, password) {
try {
validateEmail2(email);
const userCredential = await auth.createUserWithEmailAndPassword(this.auth, email, password);
const user = mapFirebaseUserToUser(userCredential.user);
const accessToken = await getFirebaseAccessToken(userCredential.user);
const session = user ? {
user,
access_token: accessToken,
refresh_token: void 0,
expires_at: void 0
} : null;
return { user, session, error: null };
} catch (error) {
return { user: null, session: null, error: mapFirebaseError(error) };
}
}
async signInWithOAuth(provider, redirectTo) {
try {
const authProvider = getOAuthProvider(provider);
if (redirectTo) {
console.warn(
"redirectTo is not directly supported for Firebase signInWithPopup. Consider using signInWithRedirect or handling redirects manually."
);
}
await auth.signInWithPopup(this.auth, authProvider);
} catch (error) {
if (error instanceof AuthError) {
throw error;
}
throw mapFirebaseError(error);
}
}
async signInWithMagicLink(email, redirectTo) {
try {
validateEmail2(email);
if (!redirectTo) {
throw new AuthError(
"VALIDATION_ERROR" /* VALIDATION_ERROR */,
"redirectTo is required for Firebase magic link sign-in"
);
}
await auth.sendSignInLinkToEmail(this.auth, email, {
url: redirectTo,
handleCodeInApp: true
});
} catch (error) {
if (error instanceof AuthError) {
throw error;
}
throw mapFirebaseError(error);
}
}
async signOut() {
try {
await auth.signOut(this.auth);
return { error: null };
} catch (error) {
return { error: mapFirebaseError(error) };
}
}
async resetPassword(email) {
try {
validateEmail2(email);
await auth.sendPasswordResetEmail(this.auth, email);
return { error: null };
} catch (error) {
return { error: mapFirebaseError(error) };
}
}
async getUser() {
return mapFirebaseUserToUser(this.auth.currentUser);
}
async getSession() {
try {
const currentUser = this.auth.currentUser;
if (!currentUser) return null;
const idTokenResult = await currentUser.getIdTokenResult();
const user = mapFirebaseUserToUser(currentUser);
return {
user,
access_token: idTokenResult.token,
refresh_token: void 0,
// Firebase doesn't expose refresh token in IdTokenResult
expires_at: idTokenResult.expirationTime ? new Date(idTokenResult.expirationTime).getTime() : void 0
};
} catch (error) {
throw mapFirebaseError(error);
}
}
onAuthStateChange(callback) {
const unsubscribe = auth.onAuthStateChanged(this.auth, async (user) => {
const mappedUser = mapFirebaseUserToUser(user);
let authEvent;
let authSession = null;
if (user) {
authEvent = "SIGNED_IN" /* SIGNED_IN */;
try {
const idTokenResult = await user.getIdTokenResult();
authSession = {
user: mappedUser,
access_token: idTokenResult.token,
refresh_token: void 0,
// Firebase doesn't expose refresh token
expires_at: idTokenResult.expirationTime ? new Date(idTokenResult.expirationTime).getTime() : void 0
};
} catch (error) {
console.error("Error getting Firebase session in onAuthStateChange:", error);
const accessToken = await getFirebaseAccessToken(user);
authSession = {
user: mappedUser,
access_token: accessToken,
refresh_token: void 0,
expires_at: void 0
};
}
} else {
authEvent = "SIGNED_OUT" /* SIGNED_OUT */;
}
callback(authEvent, authSession);
});
return { unsubscribe };
}
async updateProfile(updates) {
try {
const currentUser = this.auth.currentUser;
if (!currentUser) {
return {
user: null,
error: new AuthError(
"INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */,
"User must be authenticated to update profile"
)
};
}
const profileUpdate = {};
if (updates.name !== void 0) {
profileUpdate.displayName = updates.name;
} else if (updates.fullName !== void 0) {
profileUpdate.displayName = updates.fullName;
} else if (updates.firstName || updates.lastName) {
const firstName = updates.firstName || "";
const lastName = updates.lastName || "";
profileUpdate.displayName = `${firstName} ${lastName}`.trim() || null;
}
if (updates.avatar !== void 0) {
profileUpdate.photoURL = updates.avatar;
}
if (Object.keys(profileUpdate).length > 0) {
await auth.updateProfile(currentUser, profileUpdate);
}
const unsupportedFields = Object.keys(updates).filter(
(key) => !["name", "firstName", "lastName", "fullName", "avatar"].includes(key)
);
if (unsupportedFields.length > 0) {
console.warn(
`Firebase updateProfile: The following fields are not natively supported by Firebase Auth and were ignored: ${unsupportedFields.join(", ")}. Consider storing extended user data in Firestore or Firebase Realtime Database.`
);
}
const updatedUser = mapFirebaseUserToUser(currentUser);
return { user: updatedUser, error: null };
} catch (error) {
return { user: null, error: mapFirebaseError(error) };
}
}
};
// src/config/storage.ts
var BrowserStorage = class {
async getItem(key) {
try {
return localStorage.getItem(key);
} catch {
console.warn("localStorage not available");
return null;
}
}
async setItem(key, value) {
try {
localStorage.setItem(key, value);
} catch {
console.warn("localStorage setItem failed");
}
}
async removeItem(key) {
try {
localStorage.removeItem(key);
} catch {
console.warn("localStorage removeItem failed");
}
}
};
var ReactNativeStorage = class {
AsyncStorage;
constructor() {
try {
const AsyncStorage = new Function("return require('@react-native-async-storage/async-storage')")();
this.AsyncStorage = AsyncStorage.default || AsyncStorage;
} catch {
console.warn("AsyncStorage not available - falling back to memory storage");
this.AsyncStorage = null;
}
}
async getItem(key) {
if (!this.AsyncStorage) return null;
try {
return await this.AsyncStorage.getItem(key);
} catch {
console.warn("AsyncStorage getItem failed");
return null;
}
}
async setItem(key, value) {
if (!this.AsyncStorage) return;
try {
await this.AsyncStorage.setItem(key, value);
} catch {
console.warn("AsyncStorage setItem failed");
}
}
async removeItem(key) {
if (!this.AsyncStorage) return;
try {
await this.AsyncStorage.removeItem(key);
} catch {
console.warn("AsyncStorage removeItem failed");
}
}
};
var MemoryStorage = class {
storage = /* @__PURE__ */ new Map();
async getItem(key) {
return this.storage.get(key) || null;
}
async setItem(key, value) {
this.storage.set(key, value);
}
async removeItem(key) {
this.storage.delete(key);
}
};
var createAuthStorage = () => {
if (typeof window !== "undefined" && window.localStorage) {
return new BrowserStorage();
}
if (typeof global !== "undefined" && global.navigator?.product === "ReactNative") {
return new ReactNativeStorage();
}
if (typeof process !== "undefined" && process.versions?.node) {
return new MemoryStorage();
}
return new MemoryStorage();
};
// src/core/AuthProviderFactory.ts
var AuthProviderFactory = class {
static instances = /* @__PURE__ */ new Map();
/**
* Gets provider based on environment variables (auto-detection)
* @param config Optional configuration overrides
* @returns Authentication provider instance
*/
static async getProviderFromEnvironment(config) {
const detectedType = this.detectProviderFromEnvironment();
return this.getProvider({
type: detectedType,
validateConfig: true,
useSingleton: true,
...config
});
}
/**
* Gets provider instance with configuration
* @param config Provider configuration
* @returns Authentication provider instance
*/
static async getProvider(config) {
const { type, validateConfig: validateConfig2 = false, useSingleton = false, storage } = config;
if (useSingleton && this.instances.has(type)) {
const instance = this.instances.get(type);
if (validateConfig2 && instance.validateConfiguration) {
await instance.validateConfiguration();
}
return instance;
}
const provider = this.createProvider(type, storage);
if (validateConfig2 && provider.validateConfiguration) {
await provider.validateConfiguration();
}
if (useSingleton) {
this.instances.set(type, provider);
}
return provider;
}
/**
* Creates a new provider instance without caching
* @param type Provider type
* @param storage Optional custom storage implementation
* @returns Authentication provider instance
*/
static createProvider(type, storage) {
const authStorage = storage || createAuthStorage();
switch (type) {
case "supabase":
return new SupabaseAuthProvider(authStorage);
case "firebase":
return new FirebaseAuthProvider(authStorage);
default:
throw new AuthError(
"CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
`Unsupported authentication provider: ${type}. Supported providers: supabase, firebase`
);
}
}
/**
* Detects provider type from environment variables
* @returns Detected provider type
*/
static detectProviderFromEnvironment() {
const config = getAuthConfig();
const hasSupabaseConfig = !!(config.supabaseUrl && config.supabaseAnonKey);
const hasFirebaseConfig = !!(config.firebaseApiKey && config.firebaseAuthDomain && config.firebaseProjectId);
const explicitProvider = this.getExplicitProvider();
if (explicitProvider && this.isValidProviderType(explicitProvider)) {
return explicitProvider;
}
if (hasSupabaseConfig && !hasFirebaseConfig) {
return "supabase";
}
if (hasFirebaseConfig && !hasSupabaseConfig) {
return "firebase";
}
if (hasSupabaseConfig && hasFirebaseConfig) {
console.warn(
"Both Supabase and Firebase configurations detected. Set AUTH_PROVIDER environment variable to explicitly choose a provider. Defaulting to Supabase."
);
return "supabase";
}
throw new AuthError(
"CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
"No authentication provider configuration found. Please configure either Supabase or Firebase environment variables."
);
}
/**
* Gets explicit provider from environment variables (platform-agnostic)
*/
static getExplicitProvider() {
if (typeof process !== "undefined" && process.env) {
return process.env.NEXT_PUBLIC_AUTH_PROVIDER || process.env.AUTH_PROVIDER;
}
try {
const Constants = new Function("return require('expo-constants').default")();
return Constants.expoConfig?.extra?.authProvider;
} catch {
}
try {
const Config = new Function("return require('react-native-config').default")();
return Config.AUTH_PROVIDER;
} catch {
}
return void 0;
}
/**
* Validates if a string is a valid provider type
* @param type String to validate
* @returns True if valid provider type
*/
static isValidProviderType(type) {
return type === "supabase" || type === "firebase";
}
/**
* Gets list of supported provider types
* @returns Array of supported provider types
*/
static getSupportedProviders() {
return ["supabase", "firebase"];
}
/**
* Checks if a provider type is supported
* @param type Provider type to check
* @returns True if supported
*/
static isProviderSupported(type) {
return this.isValidProviderType(type);
}
/**
* Clears singleton instances (useful for testing)
*/
static clearInstances() {
this.instances.clear();
}
/**
* Gets current singleton instances (useful for debugging)
* @returns Map of current instances
*/
static getInstances() {
return new Map(this.instances);
}
};
var useAuthStore = zustand.create()(
middleware.devtools(
middleware.persist(
immer.immer((set, get) => ({
// State
user: null,
session: null,
// Added session to the store
loading: true,
error: null,
authProvider: null,
// Initialize authProvider as null
// Basic state actions
setUser: (user) => set((state) => {
state.user = user;
state.error = null;
}),
setSession: (session) => set((state) => {
state.session = session;
}),
// Setter for session
setLoading: (loading) => set((state) => {
state.loading = loading;
}),
setError: (error) => set((state) => {
state.error = error;
}),
setAuthProvider: (provider) => set((state) => {
state.authProvider = provider;
}),
// Authentication actions
signIn: async (email, password) => {
const provider = get().authProvider;
if (!provider) throw new Error("Auth provider not initialized.");
set((state) => {
state.loading = true;
state.error = null;
});
try {
const { user, error } = await provider.signIn(email, password);
if (error) throw error;
set((state) => {
state.user = user;
state.loading = false;
});
} catch (error) {
set((state) => {
state.error = error instanceof Error ? error : new Error(String(error));
state.loading = false;
});
throw error;
}
},
signUp: async (email, password) => {
const provider = get().authProvider;
if (!provider) throw new Error("Auth provider not initialized.");
set((state) => {
state.loading = true;
state.error = null;
});
try {
const { user, error } = await provider.signUp(email, password);
if (error) throw error;
set((state) => {
state.user = user;
state.loading = false;
});
} catch (error) {
set((state) => {
state.error = error instanceof Error ? error : new Error(String(error));
state.loading = false;
});
throw error;
}
},
signOut: async () => {
const provider = get().authProvider;
if (!provider) throw new Error("Auth provider not initialized.");
set((state) => {
state.loading = true;
state.error = null;
});
try {
const { error } = await provider.signOut();
if (error) throw error;
set((state) => {
state.user = null;
state.session = null;
state.loading = false;
});
} catch (error) {
set((state) => {
state.error = error instanceof Error ? error : new Error(String(error));
state.loading = false;
});
throw error;
}
},
resetPassword: async (email) => {
const provider = get().authProvider;
if (!provider) throw new Error("Auth provider not initialized.");
set((state) => {
state.loading = true;
state.error = null;
});
try {
const { error } = await provider.resetPassword(email);
if (error) throw error;
set((state) => {
state.loading = false;
});
} catch (error) {
set((state) => {
state.error = error instanceof Error ? error : new Error(String(error));
state.loading = false;
});
throw error;
}
},
updateProfile: async (updates) => {
const provider = get().authProvider;
if (!provider) throw new Error("Auth provider not initialized.");
set((state) => {
state.loading = true;
state.error = null;
});
try {
const { user, error } = await provider.updateProfile(updates);
if (error) throw error;
set((state) => {
state.user = user;
if (state.session && user) {
state.session.user = user;
}
state.loading = false;
});
return user;
} catch (error) {
set((state) => {
state.error = error instanceof Error ? error : new Error(String(error));
state.loading = false;
});
throw error;
}
},
// Extended auth methods (delegated to provider)
signInWithOAuth: async (providerName, redirectTo) => {
const provider = get().authProvider;
if (!provider) throw new Error("Auth provider not initialized.");
set((state) => {
state.loading = true;
state.error = null;
});
try {
await provider.signInWithOAuth(providerName, redirectTo);
set((state) => {
state.loading = false;
});
} catch (error) {
set((state) => {
state.error = error instanceof Error ? error : new Error(String(error));
state.loading = false;
});
throw error;
}
},
signInWithMagicLink: async (email, redirectTo) => {
const provider = get().authProvider;
if (!provider) throw new Error("Auth provider not initialized.");
set((state) => {
state.loading = true;
state.error = null;
});
try {
await provider.signInWithMagicLink(email, redirectTo);
set((state) => {
state.loading = false;
});
} catch (error) {
set((state) => {
state.error = error instanceof Error ? error : new Error(String(error));
state.loading = false;
});
throw error;
}
},
getUser: async () => {
const provider = get().authProvider;
if (!provider) throw new Error("Auth provider not initialized.");
try {
const user = await provider.getUser();
set((state) => {
state.user = user;
});
return user;
} catch (error) {
set((state) => {
state.error = error instanceof Error ? error : new Error(String(error));
});
throw error;
}
},
getSession: async () => {
const provider = get().authProvider;
if (!provider) throw new Error("Auth provider not initialized.");
try {
return await provider.getSession();
} catch (error) {
set((state) => {
state.error = error instanceof Error ? error : new Error(String(error));
});
throw error;
}
},
onAuthStateChange: (callback) => {
const provider = get().authProvider;
if (!provider) throw new Error("Auth provider not initialized.");
return provider.onAuthStateChange(callback);
}
})),
{
name: "auth-storage",
storage: middleware.createJSONStorage(() => localStorage),
partialize: (state) => Object.fromEntries(
Object.entries(state).filter(([key]) => ["user", "session"].includes(key))
)
}
)
)
);
var AuthProvider = ({
children,
onAuthChange,
autoSignIn = true
}) => {
const authProviderRef = react.useRef(null);
const unsubscribeRef = react.useRef(null);
const setUser = useAuthStore((state) => state.setUser);
const setSession = useAuthStore((state) => state.setSession);
const setLoading = useAuthStore((state) => state.setLoading);
const setError = useAuthStore((state) => state.setError);
const setAuthProvider = useAuthStore((state) => state.setAuthProvider);
react.useEffect(() => {
let mounted = true;
const initializeAuth = async () => {
try {
const provider = await AuthProviderFactory.getProviderFromEnvironment({
validateConfig: true,
useSingleton: true
});
if (!mounted) return;
authProviderRef.current = provider;
setAuthProvider(provider);
const subscription = provider.onAuthStateChange((event, session) => {
if (!mounted) return;
if (session) {
setUser(session.user);
setSession(session);
setError(null);
} else {
setUser(null);
setSession(null);
}
if (onAuthChange) {
onAuthChange(event);
}
setLoading(false);
});
if (subscription?.unsubscribe) {
unsubscribeRef.current = subscription.unsubscribe;
}
if (autoSignIn) {
try {
const currentSession = await provider.getSession();
if (mounted) {
if (currentSession) {
setUser(currentSession.user);
setSession(currentSession);
} else {
setUser(null);
setSession(null);
}
setLoading(false);
}
} catch (error) {
console.error("Failed to get initial session:", error);
if (mounted) {
setError(error instanceof Error ? error : new Error("Failed to get session"));
setLoading(false);
}
}
} else {
if (mounted) {
setLoading(false);
}
}
} catch (error) {
console.error("Failed to initialize auth provider:", error);
if (mounted) {
setError(error instanceof Error ? error : new Error("Failed to initialize auth"));
setLoading(false);
}
}
};
initializeAuth();
return () => {
mounted = false;
if (unsubscribeRef.current) {
unsubscribeRef.current();
unsubscribeRef.current = null;
}
};
}, []);
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
};
// src/hooks/useAuth.ts
var useAuth = () => {
const authState = useAuthStore();
if (!authState.authProvider && !authState.loading) {
console.warn(
"Auth provider not initialized. Make sure to wrap your app with <AuthProvider> component."
);
}
return authState;
};
// src/actions/index.ts
var signUp = async (email, password) => {
const store = useAuthStore.getState();
return store.signUp(email, password);
};
var signIn = async (email, password) => {
const store = useAuthStore.getState();
return store.signIn(email, password);
};
var signOut = async () => {
const store = useAuthStore.getState();
return store.signOut();
};
var resetPassword = async (email) => {
const store = useAuthStore.getState();
return store.resetPassword(email);
};
var updateProfile = async (updates) => {
const store = useAuthStore.getState();
if (!store.user) {
throw new Error("No user logged in");
}
return await store.updateProfile(updates);
};
var signInWithOAuth = async (provider, redirectTo) => {
const store = useAuthStore.getState();
return store.signInWithOAuth(provider, redirectTo);
};
var signInWithMagicLink = async (email, redirectTo) => {
const store = useAuthStore.getState();
return store.signInWithMagicLink(email, redirectTo);
};
var createClient2 = () => {
if (!process.env.NEXT_PUBLIC_SUPABASE_URL || !process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) {
throw new Error("Missing Supabase environment variables");
}
return ssr.createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
);
};
(() => {
try {
if (process.env.NEXT_PUBLIC_SUPABASE_URL && process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) {
return createClient2();
}
} catch {
console.warn("Supabase client not initialized: environment variables not available");
}
return null;
})();
function withAuth(request) {
let response = server_js.NextResponse.next({
request: {
headers: request.headers
}
});
const supabase2 = ssr.createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.e