- 웹은 기존 GIS 유지, Capacitor 네이티브(APK)는 네이티브 구글 로그인 사용 (웹뷰에서 GIS 팝업/FedCM 미동작 대응) - 받은 ID 토큰은 기존과 동일하게 백엔드 /api/auth/google 로 검증 - src/lib/googleAuth.ts + LoginPage 네이티브/웹 분기 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Generated
+13
@@ -12,6 +12,7 @@
|
||||
"@capacitor/app": "^8.0.0",
|
||||
"@capacitor/core": "^8.3.4",
|
||||
"@capacitor/geolocation": "^8.2.0",
|
||||
"@capgo/capacitor-social-login": "^8.3.36",
|
||||
"@quasar/extras": "^1.16.12",
|
||||
"@stomp/stompjs": "^7.3.0",
|
||||
"pinia": "^2.3.0",
|
||||
@@ -187,6 +188,18 @@
|
||||
"integrity": "sha512-/C1FUo8/OkKuAT4nCIu/34ny9siNHr9qtFezu4kxm6GY1wNFxrCFWjfYx5C1tUhVGz3fxBABegupkpjXvjCHrw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@capgo/capacitor-social-login": {
|
||||
"version": "8.3.36",
|
||||
"resolved": "https://registry.npmjs.org/@capgo/capacitor-social-login/-/capacitor-social-login-8.3.36.tgz",
|
||||
"integrity": "sha512-Y4HYjEJablzbHHwPBrbC2UvHVPsR0bAyCAqBzVBoUEOf0UFHAHyTP3gD9JUTKP/iERU8I+6IA2RWtLqDePCNuA==",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@capacitor/core": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"@capacitor/app": "^8.0.0",
|
||||
"@capacitor/core": "^8.3.4",
|
||||
"@capacitor/geolocation": "^8.2.0",
|
||||
"@capgo/capacitor-social-login": "^8.3.36",
|
||||
"@quasar/extras": "^1.16.12",
|
||||
"@stomp/stompjs": "^7.3.0",
|
||||
"pinia": "^2.3.0",
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Capacitor } from '@capacitor/core'
|
||||
import { SocialLogin } from '@capgo/capacitor-social-login'
|
||||
|
||||
/**
|
||||
* 네이티브(Capacitor) 구글 로그인.
|
||||
* 웹뷰에서는 GIS(팝업/FedCM)가 동작하지 않으므로, 앱에서는 이 네이티브 흐름을 사용한다.
|
||||
* 받은 ID 토큰은 웹 GIS 와 동일하게 백엔드 /api/auth/google 로 검증한다.
|
||||
*/
|
||||
|
||||
let initializedFor = ''
|
||||
|
||||
/** 네이티브 플랫폼(iOS/Android) 여부 — 웹은 기존 GIS 사용. */
|
||||
export function isNativeGoogleAvailable(): boolean {
|
||||
return Capacitor.isNativePlatform()
|
||||
}
|
||||
|
||||
/**
|
||||
* 네이티브 구글 로그인 → ID 토큰 반환.
|
||||
* @param webClientId 백엔드가 aud 검증에 쓰는 웹 클라이언트 ID (serverClientId)
|
||||
*/
|
||||
export async function signInWithGoogle(webClientId: string): Promise<string> {
|
||||
if (initializedFor !== webClientId) {
|
||||
await SocialLogin.initialize({ google: { webClientId } })
|
||||
initializedFor = webClientId
|
||||
}
|
||||
const res = await SocialLogin.login({
|
||||
provider: 'google',
|
||||
options: { scopes: ['email', 'profile'] },
|
||||
})
|
||||
const result = res.result as { idToken?: string | null }
|
||||
if (!result?.idToken) {
|
||||
throw new Error('구글 ID 토큰을 받지 못했습니다.')
|
||||
}
|
||||
return result.idToken
|
||||
}
|
||||
+36
-2
@@ -12,8 +12,20 @@
|
||||
</div>
|
||||
|
||||
<q-card flat class="login-card q-pa-lg">
|
||||
<!-- 구글 로그인 버튼(GIS 렌더링 영역) -->
|
||||
<div ref="googleBtn" class="flex flex-center q-mb-md"></div>
|
||||
<!-- 구글 로그인: 앱(네이티브)은 버튼, 웹은 GIS 렌더링 -->
|
||||
<q-btn
|
||||
v-if="isNative"
|
||||
class="full-width q-py-sm q-mb-md"
|
||||
color="white"
|
||||
text-color="grey-9"
|
||||
unelevated
|
||||
icon="mdi-google"
|
||||
label="Google로 계속하기"
|
||||
:loading="googleLoading"
|
||||
:disable="!googleClientId"
|
||||
@click="onGoogleNative"
|
||||
/>
|
||||
<div v-else ref="googleBtn" class="flex flex-center q-mb-md"></div>
|
||||
<div v-if="googleError" class="text-caption text-negative text-center q-mb-md">
|
||||
{{ googleError }}
|
||||
</div>
|
||||
@@ -63,6 +75,7 @@ import { useAuthStore } from '@/stores/auth'
|
||||
import { authApi } from '@/api/auth'
|
||||
import { ApiError } from '@/api/http'
|
||||
import { isAppleSignInAvailable, signInWithApple } from '@/lib/appleAuth'
|
||||
import { isNativeGoogleAvailable, signInWithGoogle } from '@/lib/googleAuth'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
@@ -71,6 +84,9 @@ const auth = useAuthStore()
|
||||
|
||||
const googleBtn = ref<HTMLElement | null>(null)
|
||||
const googleError = ref('')
|
||||
const googleClientId = ref('')
|
||||
const googleLoading = ref(false)
|
||||
const isNative = isNativeGoogleAvailable()
|
||||
const appleLoading = ref(false)
|
||||
const appleAvailable = isAppleSignInAvailable()
|
||||
const devLoading = ref(false)
|
||||
@@ -85,6 +101,9 @@ async function initGoogle() {
|
||||
googleError.value = '구글 로그인이 아직 설정되지 않았습니다.'
|
||||
return
|
||||
}
|
||||
googleClientId.value = clientId
|
||||
// 앱(네이티브)은 버튼 클릭 시 네이티브 로그인 → GIS 초기화 불필요
|
||||
if (isNative) return
|
||||
await loadGsi()
|
||||
const google = (window as unknown as { google: any }).google
|
||||
google.accounts.id.initialize({
|
||||
@@ -124,6 +143,21 @@ function loadGsi(): Promise<void> {
|
||||
})
|
||||
}
|
||||
|
||||
async function onGoogleNative() {
|
||||
if (!googleClientId.value) return
|
||||
googleLoading.value = true
|
||||
try {
|
||||
const idToken = await signInWithGoogle(googleClientId.value)
|
||||
await auth.loginGoogle(idToken)
|
||||
redirectAfterLogin()
|
||||
} catch (e) {
|
||||
if (isAppleCancel(e)) return
|
||||
notifyError(e)
|
||||
} finally {
|
||||
googleLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onApple() {
|
||||
appleLoading.value = true
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user