fix(mobile): prevent supabaseUrl crash on startup with fallback auth config

This commit is contained in:
Yun Chan 2026-08-20 23:29:10 +09:00
parent 9b24bbc576
commit a9c9a1ca6e
4 changed files with 35 additions and 17 deletions

View file

@ -2,7 +2,7 @@
import { createContext, useContext, useEffect, useState, useCallback } from 'react' import { createContext, useContext, useEffect, useState, useCallback } from 'react'
import type { ReactNode } from 'react' import type { ReactNode } from 'react'
import type { User, Session } from '@supabase/supabase-js' import type { User, Session } from '@supabase/supabase-js'
import { supabase } from './supabase' import { supabase, isSupabaseConfigured } from './supabase'
interface AuthContextValue { interface AuthContextValue {
user: User | null user: User | null
@ -14,28 +14,41 @@ interface AuthContextValue {
const AuthContext = createContext<AuthContextValue>({ const AuthContext = createContext<AuthContextValue>({
user: null, user: null,
session: null, session: null,
loading: true, loading: false,
devBypass: () => {}, devBypass: () => {},
}) })
export function AuthProvider({ children }: { children: ReactNode }): React.ReactElement { export function AuthProvider({ children }: { children: ReactNode }): React.ReactElement {
const [user, setUser] = useState<User | null>(null) const [user, setUser] = useState<User | null>(null)
const [session, setSession] = useState<Session | null>(null) const [session, setSession] = useState<Session | null>(null)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(false)
useEffect(() => { useEffect(() => {
supabase.auth.getSession().then(({ data: { session: s } }) => { if (!isSupabaseConfigured()) {
setLoading(false)
return
}
try {
supabase.auth.getSession()
.then(({ data: { session: s } }) => {
setSession(s) setSession(s)
setUser(s?.user ?? null) setUser(s?.user ?? null)
setLoading(false) setLoading(false)
}) })
.catch(() => {
setLoading(false)
})
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, s) => { const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, s) => {
setSession(s) setSession(s)
setUser(s?.user ?? null) setUser(s?.user ?? null)
}) })
return () => subscription.unsubscribe() return () => subscription?.unsubscribe?.()
} catch {
setLoading(false)
}
}, []) }, [])
const devBypass = useCallback(() => { const devBypass = useCallback(() => {
@ -53,3 +66,4 @@ export function AuthProvider({ children }: { children: ReactNode }): React.React
export function useAuth(): AuthContextValue { export function useAuth(): AuthContextValue {
return useContext(AuthContext) return useContext(AuthContext)
} }

View file

@ -3,13 +3,16 @@ import 'react-native-url-polyfill/auto'
import AsyncStorage from '@react-native-async-storage/async-storage' import AsyncStorage from '@react-native-async-storage/async-storage'
import { createClient, type SupabaseClient } from '@supabase/supabase-js' import { createClient, type SupabaseClient } from '@supabase/supabase-js'
// TODO: move to env config or supabase-config.ts // Fallback dummy credentials to prevent createClient crash when unconfigured
const FALLBACK_URL = 'https://auth.d3ro.chanpaca.net'
const FALLBACK_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.dummy_anon_key'
const SUPABASE_URL = '' const SUPABASE_URL = ''
const SUPABASE_ANON_KEY = '' const SUPABASE_ANON_KEY = ''
export const supabase: SupabaseClient = createClient( export const supabase: SupabaseClient = createClient(
SUPABASE_URL, SUPABASE_URL || FALLBACK_URL,
SUPABASE_ANON_KEY, SUPABASE_ANON_KEY || FALLBACK_ANON_KEY,
{ {
auth: { auth: {
storage: AsyncStorage, storage: AsyncStorage,
@ -23,3 +26,4 @@ export const supabase: SupabaseClient = createClient(
export function isSupabaseConfigured(): boolean { export function isSupabaseConfigured(): boolean {
return SUPABASE_URL.length > 0 && SUPABASE_ANON_KEY.length > 0 return SUPABASE_URL.length > 0 && SUPABASE_ANON_KEY.length > 0
} }

View file

@ -20,7 +20,7 @@ export const OS_CONFIGS: Record<ClientOSType, ClientOSInfo> = {
os: 'android', os: 'android',
osName: 'Android', osName: 'Android',
isMobile: true, isMobile: true,
downloadUrl: 'https://git.chanpaca.net/attachments/5220bdb9-e8ce-4d6a-b016-dab08dd756b8', downloadUrl: 'https://git.chanpaca.net/attachments/0b015367-dd8b-488c-8cc0-4db413b51792',
downloadFilename: 'd3ro-voice-v1.0.0-signed.apk', downloadFilename: 'd3ro-voice-v1.0.0-signed.apk',
badgeText: 'ANDROID APK v1.0.0', badgeText: 'ANDROID APK v1.0.0',
buttonLabelKo: 'Android용 무료 다운로드 (APK)', buttonLabelKo: 'Android용 무료 다운로드 (APK)',

View file

@ -277,7 +277,7 @@ export function Download() {
<div className="space-y-2"> <div className="space-y-2">
<a <a
href="https://git.chanpaca.net/attachments/5220bdb9-e8ce-4d6a-b016-dab08dd756b8" href="https://git.chanpaca.net/attachments/0b015367-dd8b-488c-8cc0-4db413b51792"
download="d3ro-voice-v1.0.0-signed.apk" download="d3ro-voice-v1.0.0-signed.apk"
className="w-full py-2.5 px-4 rounded-lg bg-surface-700 hover:bg-surface-600 border border-white/[0.08] hover:border-emerald-400/40 text-neutral-200 font-mono text-xs flex items-center justify-between transition-all" className="w-full py-2.5 px-4 rounded-lg bg-surface-700 hover:bg-surface-600 border border-white/[0.08] hover:border-emerald-400/40 text-neutral-200 font-mono text-xs flex items-center justify-between transition-all"
> >