Skip to content

Commit 7a3d7e2

Browse files
YuvalYuval
authored andcommitted
fixed android google login
1 parent 7b02d5c commit 7a3d7e2

6 files changed

Lines changed: 157 additions & 49 deletions

File tree

03-Client/android/app/capacitor.build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ dependencies {
1515
implementation project(':capacitor-geolocation')
1616
implementation project(':capacitor-splash-screen')
1717
implementation project(':capacitor-status-bar')
18+
implementation project(':capgo-capacitor-social-login')
1819

1920
}
2021

03-Client/android/app/src/main/res/values/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,9 @@
44
<string name="title_activity_main">GroundShare</string>
55
<string name="package_name">com.groundshare.app</string>
66
<string name="custom_url_scheme">com.groundshare.app</string>
7+
<!-- Google Sign-In: this is the WEB client ID, not the Android one.
8+
The Android OAuth client in Google Cloud Console only authorizes our
9+
package + SHA-1; the token's audience must match the backend's
10+
Google:ClientId (which is the Web client ID). See AuthController.cs. -->
11+
<string name="server_client_id">82985006549-i2dn8m92d760lsdcsvj4sao19a4e46et.apps.googleusercontent.com</string>
712
</resources>

03-Client/android/capacitor.settings.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ project(':capacitor-splash-screen').projectDir = new File('../node_modules/@capa
1919

2020
include ':capacitor-status-bar'
2121
project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android')
22+
23+
include ':capgo-capacitor-social-login'
24+
project(':capgo-capacitor-social-login').projectDir = new File('../node_modules/@capgo/capacitor-social-login/android')

03-Client/package-lock.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

03-Client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"@capacitor/ios": "^8.3.1",
2424
"@capacitor/splash-screen": "^8.0.1",
2525
"@capacitor/status-bar": "^8.0.2",
26+
"@capgo/capacitor-social-login": "^8.3.17",
2627
"@emotion/react": "11.14.0",
2728
"@emotion/styled": "11.14.1",
2829
"@mui/icons-material": "7.3.5",

03-Client/src/app/components/shared/SocialLoginButtons.tsx

Lines changed: 134 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
import { useEffect, useRef, useState } from "react";
22
import { useNavigate } from "react-router";
3+
import { Capacitor } from "@capacitor/core";
4+
import { SocialLogin } from "@capgo/capacitor-social-login";
35
import { useAuth } from "../../context/AuthContext";
46

5-
// Google OAuth client ID — safe to expose (public identifier, not a secret)
7+
// Google OAuth Web Client ID — safe to expose (public identifier, not a secret).
8+
// Used in both flows:
9+
// - Web: passed to Google Identity Services `initialize({ client_id })`.
10+
// - Native: passed as `webClientId` to the Capacitor plugin. The plugin
11+
// asks Android's native Sign-In for an ID token whose `aud` equals this
12+
// value, so our backend (which validates `aud` against Google:ClientId)
13+
// accepts tokens from web AND native with zero backend changes.
614
const GOOGLE_CLIENT_ID =
715
"82985006549-i2dn8m92d760lsdcsvj4sao19a4e46et.apps.googleusercontent.com";
816

@@ -79,15 +87,30 @@ function loadGoogleScript(): Promise<void> {
7987
document.head.appendChild(script);
8088
});
8189

82-
// If it rejects, allow a later mount to try again rather than being
83-
// permanently stuck on the cached rejection.
8490
gsiLoadPromise.catch(() => {
8591
gsiLoadPromise = null;
8692
});
8793

8894
return gsiLoadPromise;
8995
}
9096

97+
// Module-level guard so we only call SocialLogin.initialize() once per app
98+
// launch. The plugin throws if initialize is called twice.
99+
let nativeInitPromise: Promise<void> | null = null;
100+
101+
function initNativeSocialLogin(): Promise<void> {
102+
if (nativeInitPromise) return nativeInitPromise;
103+
nativeInitPromise = SocialLogin.initialize({
104+
google: {
105+
webClientId: GOOGLE_CLIENT_ID,
106+
},
107+
});
108+
nativeInitPromise.catch(() => {
109+
nativeInitPromise = null;
110+
});
111+
return nativeInitPromise;
112+
}
113+
91114
interface SocialLoginButtonsProps {
92115
label?: string;
93116
onSuccess?: () => void;
@@ -101,7 +124,44 @@ export function SocialLoginButtons({ label = "התחברות באמצעות", on
101124
const [ready, setReady] = useState(false);
102125
const buttonHostRef = useRef<HTMLDivElement | null>(null);
103126

127+
const isNative = Capacitor.isNativePlatform();
128+
129+
async function handleGoogleSuccess(idToken: string) {
130+
setLoading(true);
131+
setError(null);
132+
try {
133+
await loginWithGoogle(idToken);
134+
if (onSuccess) onSuccess();
135+
else navigate("/main");
136+
} catch (err: unknown) {
137+
const msg = (err as { message?: string })?.message ?? "שגיאה בהתחברות עם Google";
138+
setError(msg);
139+
} finally {
140+
setLoading(false);
141+
}
142+
}
143+
144+
// Native platforms: initialize the plugin once on mount, then show our
145+
// branded button whose onClick triggers the native account picker.
104146
useEffect(() => {
147+
if (!isNative) return;
148+
let cancelled = false;
149+
initNativeSocialLogin()
150+
.then(() => {
151+
if (!cancelled) setReady(true);
152+
})
153+
.catch(() => {
154+
if (!cancelled) setError("לא ניתן לטעון את שירות Google");
155+
});
156+
return () => {
157+
cancelled = true;
158+
};
159+
}, [isNative]);
160+
161+
// Web platform: load Google Identity Services and overlay the transparent
162+
// Google-rendered button on top of our branded visual.
163+
useEffect(() => {
164+
if (isNative) return;
105165
let cancelled = false;
106166

107167
loadGoogleScript()
@@ -116,18 +176,7 @@ export function SocialLoginButtons({ label = "התחברות באמצעות", on
116176
client_id: GOOGLE_CLIENT_ID,
117177
callback: async (response: { credential: string }) => {
118178
if (!response?.credential) return;
119-
setLoading(true);
120-
setError(null);
121-
try {
122-
await loginWithGoogle(response.credential);
123-
if (onSuccess) onSuccess();
124-
else navigate("/main");
125-
} catch (err: unknown) {
126-
const msg = (err as { message?: string })?.message ?? "שגיאה בהתחברות עם Google";
127-
setError(msg);
128-
} finally {
129-
setLoading(false);
130-
}
179+
await handleGoogleSuccess(response.credential);
131180
},
132181
use_fedcm_for_prompt: true,
133182
auto_select: false,
@@ -153,7 +202,37 @@ export function SocialLoginButtons({ label = "התחברות באמצעות", on
153202
return () => {
154203
cancelled = true;
155204
};
156-
}, [loginWithGoogle, navigate, onSuccess]);
205+
// handleGoogleSuccess captures loginWithGoogle/navigate/onSuccess via closure;
206+
// we intentionally only re-run when the platform type changes.
207+
// eslint-disable-next-line react-hooks/exhaustive-deps
208+
}, [isNative]);
209+
210+
async function handleNativeClick() {
211+
if (!ready || loading) return;
212+
setError(null);
213+
try {
214+
const res = await SocialLogin.login({
215+
provider: "google",
216+
options: {},
217+
});
218+
// Plugin response shape: { provider, result: { idToken, accessToken, profile, ... } }
219+
const result = res as { result?: { idToken?: string } };
220+
const idToken = result.result?.idToken;
221+
if (!idToken) {
222+
setError("לא התקבל טוקן מ-Google");
223+
return;
224+
}
225+
await handleGoogleSuccess(idToken);
226+
} catch (err: unknown) {
227+
const msg = (err as { message?: string })?.message ?? "שגיאה בהתחברות עם Google";
228+
// User-cancelled flows on Android throw with codes like "12501" or
229+
// "canceled" — don't show a scary error for those.
230+
if (/cancel/i.test(msg) || /12501/.test(msg)) {
231+
return;
232+
}
233+
setError(msg);
234+
}
235+
}
157236

158237
return (
159238
<div className="flex flex-col items-center gap-4">
@@ -162,41 +241,36 @@ export function SocialLoginButtons({ label = "התחברות באמצעות", on
162241
</p>
163242
<div className="flex gap-3 justify-center">
164243
{/* Google — the only provider enabled right now.
165-
Facebook and Apple are intentionally commented out until we add server-side verification for them.
166-
167-
Implementation note: Google Identity Services' `prompt()` call is unreliable
168-
on modern browsers (FedCM, third-party cookie blockers, incognito). We use
169-
`renderButton()` instead, then visually overlay our branded button on top of
170-
the Google-rendered element so clicks fall through to Google's handler.
171-
This is the pattern Google recommends and it works across browsers. */}
172-
<div className="relative w-[60px] h-[44px]">
173-
{/* Branded visual — pointer-events:none so clicks hit the Google button below */}
174-
<div
175-
aria-hidden="true"
176-
className={`absolute inset-0 pointer-events-none bg-white border border-[#dadce0] rounded-[10px] flex items-center justify-center shadow-[0px_1px_3px_rgba(0,0,0,0.08)] ${
177-
!ready || loading ? "opacity-60" : ""
178-
}`}
244+
Native: a regular <button> that calls the Capacitor plugin.
245+
Web: branded visual + transparent Google-rendered button overlay. */}
246+
{isNative ? (
247+
<button
248+
type="button"
249+
onClick={handleNativeClick}
250+
disabled={!ready || loading}
251+
aria-label="התחברות עם Google"
252+
className="bg-white border border-[#dadce0] hover:bg-[#f8f9fa] transition-colors w-[60px] h-[44px] rounded-[10px] flex items-center justify-center cursor-pointer shadow-[0px_1px_3px_rgba(0,0,0,0.08)] disabled:opacity-60"
179253
>
180-
<svg width="22" height="22" viewBox="0 0 48 48" aria-hidden="true">
181-
<path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z" />
182-
<path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z" />
183-
<path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z" />
184-
<path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z" />
185-
</svg>
254+
<GoogleLogo />
255+
</button>
256+
) : (
257+
<div className="relative w-[60px] h-[44px]">
258+
<div
259+
aria-hidden="true"
260+
className={`absolute inset-0 pointer-events-none bg-white border border-[#dadce0] rounded-[10px] flex items-center justify-center shadow-[0px_1px_3px_rgba(0,0,0,0.08)] ${
261+
!ready || loading ? "opacity-60" : ""
262+
}`}
263+
>
264+
<GoogleLogo />
265+
</div>
266+
<div
267+
ref={buttonHostRef}
268+
aria-label="התחברות עם Google"
269+
className="absolute inset-0 opacity-0"
270+
style={{ colorScheme: "light" }}
271+
/>
186272
</div>
187-
{/* Google-rendered clickable surface — transparent, sits on top, handles auth */}
188-
<div
189-
ref={buttonHostRef}
190-
aria-label="התחברות עם Google"
191-
className="absolute inset-0 opacity-0"
192-
style={{ colorScheme: "light" }}
193-
/>
194-
</div>
195-
196-
{/*
197-
// Facebook — disabled until server-side Facebook token verification is added.
198-
// Apple — disabled until server-side Apple token verification (JWKS + private key) is added.
199-
*/}
273+
)}
200274
</div>
201275
{loading && (
202276
<p className="font-['Heebo',sans-serif] text-[#626262] text-[12px]">מתחבר עם Google...</p>
@@ -209,3 +283,14 @@ export function SocialLoginButtons({ label = "התחברות באמצעות", on
209283
</div>
210284
);
211285
}
286+
287+
function GoogleLogo() {
288+
return (
289+
<svg width="22" height="22" viewBox="0 0 48 48" aria-hidden="true">
290+
<path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z" />
291+
<path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z" />
292+
<path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z" />
293+
<path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z" />
294+
</svg>
295+
);
296+
}

0 commit comments

Comments
 (0)