import 'package:firebase_auth/firebase_auth.dart' as fb; import 'package:google_sign_in/google_sign_in.dart' as gsi; import 'package:sign_in_with_apple/sign_in_with_apple.dart' as apple; import '../../core/firebase/firebase_boot.dart'; import 'auth.dart'; /// Google 로그인 — 실 코드 경로. /// /// Firebase 설정 파일이 없으면 SDK 를 부르기 전에 [AuthNotConfigured] 로 끊는다. /// 파일이 투입되면(S2) 이 클래스는 수정 없이 그대로 동작한다. final class GoogleSignInProvider implements SocialAuthProvider { const GoogleSignInProvider(); @override SocialProvider get provider => SocialProvider.google; @override bool get isLive => true; // 화면에 그대로 노출되는 목록이라 리포 경로가 아니라 사람이 읽는 이름을 쓴다. // 실제 파일 경로는 FirebaseBoot.detail 로 로그에만 남는다. @override List get missingConfig => FirebaseBoot.isLive ? const [] : FirebaseBoot.missingLabels; @override Future signIn() async { final missing = missingConfig; if (missing.isNotEmpty) return AuthNotConfigured(provider, missing); try { final signIn = gsi.GoogleSignIn.instance; // clientId/serverClientId 는 설정 파일에서 읽힌다 — 여기서 하드코딩하지 않는다. await signIn.initialize(); final account = await signIn.authenticate(); final idToken = account.authentication.idToken; if (idToken == null) { return const AuthFailed('E-AUTH-03', '소셜 로그인에 실패했습니다.'); } final credential = fb.GoogleAuthProvider.credential(idToken: idToken); final result = await fb.FirebaseAuth.instance.signInWithCredential(credential); return AuthSucceeded(_toAccount(result.user, provider)); } on gsi.GoogleSignInException catch (error) { if (error.code == gsi.GoogleSignInExceptionCode.canceled) { return const AuthCancelled(); } return AuthFailed('E-AUTH-03', error.description ?? error.code.name); } on fb.FirebaseAuthException catch (error) { return AuthFailed('E-AUTH-03', error.message ?? error.code); } } @override Future signOut() async { if (!FirebaseBoot.isLive) return; await gsi.GoogleSignIn.instance.signOut(); await fb.FirebaseAuth.instance.signOut(); } } /// Apple 로그인 — 실 코드 경로. /// /// iOS 는 Xcode 에서 「Sign in with Apple」 Capability 를, Android·Web 은 /// Apple Developer 콘솔의 Service ID + redirect URI 를 각각 요구한다. /// 둘 다 스토어 계정 명의가 정해져야(Q5) 발급할 수 있어 지금은 설정 파일 게이트에서 끊긴다. final class AppleSignInProvider implements SocialAuthProvider { const AppleSignInProvider(); @override SocialProvider get provider => SocialProvider.apple; @override bool get isLive => true; @override List get missingConfig => FirebaseBoot.isLive ? const [] : [...FirebaseBoot.missingLabels, 'Apple 개발자 설정']; @override Future signIn() async { final missing = missingConfig; if (missing.isNotEmpty) return AuthNotConfigured(provider, missing); if (!await apple.SignInWithApple.isAvailable()) { return const AuthFailed('E-AUTH-03', '이 기기에서는 Apple 로그인을 쓸 수 없습니다.'); } try { final credential = await apple.SignInWithApple.getAppleIDCredential( scopes: const [ apple.AppleIDAuthorizationScopes.email, apple.AppleIDAuthorizationScopes.fullName, ], ); final idToken = credential.identityToken; if (idToken == null) { return const AuthFailed('E-AUTH-03', '소셜 로그인에 실패했습니다.'); } final oauth = fb.OAuthProvider('apple.com').credential( idToken: idToken, accessToken: credential.authorizationCode, ); final result = await fb.FirebaseAuth.instance.signInWithCredential(oauth); return AuthSucceeded(_toAccount(result.user, provider)); } on apple.SignInWithAppleAuthorizationException catch (error) { if (error.code == apple.AuthorizationErrorCode.canceled) { return const AuthCancelled(); } return AuthFailed('E-AUTH-03', error.message); } on fb.FirebaseAuthException catch (error) { return AuthFailed('E-AUTH-03', error.message ?? error.code); } } @override Future signOut() async { if (!FirebaseBoot.isLive) return; await fb.FirebaseAuth.instance.signOut(); } } /// 앱 키가 없어 아직 붙이지 못한 제공자 — 카카오·네이버. /// /// 예외를 던지지 않는다. [AuthNotConfigured] 로 "무엇이 없어서 못 쓰는지"를 돌려준다. /// 키가 들어오면 이 자리에 실 구현체를 넣고 [AuthRepository] 목록만 바꾸면 된다. final class KeylessSocialProvider implements SocialAuthProvider { const KeylessSocialProvider(this.provider, this.missingConfig); @override final SocialProvider provider; @override final List missingConfig; @override bool get isLive => false; @override Future signIn() async => AuthNotConfigured(provider, missingConfig); @override Future signOut() async {} } AuthAccount _toAccount(fb.User? user, SocialProvider provider) => AuthAccount( uid: user?.uid ?? '', provider: provider, displayName: user?.displayName, email: user?.email, );