feat(ios): plugin natif Sign in with Google (CoachGoogleAuth)
- CoachGoogleAuth.swift : plugin Capacitor (SDK GoogleSignIn) → signIn() renvoie
{ idToken, email?, givenName? }, POSTé sur /auth/google/web (aud = client iOS).
Garde #if canImport(GoogleSignIn) → le repo build même avant l'ajout du SDK.
- MainViewController : enregistre CoachGoogleAuthPlugin.
- AppDelegate : GIDSignIn.sharedInstance.handle(url) dans application(_:open:).
- project.pbxproj : CoachGoogleAuth.swift dans la target App.
⚠️ Xcode (Mac) : ajouter le SDK GoogleSignIn-iOS via SPM + GIDClientID/URL
scheme (reversed client id) dans Info.plist (cf. HANDOFF). Sans le SDK, le
bouton ne s'affiche pas (signIn rejette proprement).
This commit is contained in:
@@ -38,6 +38,7 @@
|
|||||||
E547F675B2F61C3475D2A787 /* LiveActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B93DA6B121E94B11DF938C90 /* LiveActivityManager.swift */; };
|
E547F675B2F61C3475D2A787 /* LiveActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B93DA6B121E94B11DF938C90 /* LiveActivityManager.swift */; };
|
||||||
F1ABCDEF0123456789AB0001 /* CoachHealthRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1ABCDEF0123456789AB0002 /* CoachHealthRoute.swift */; };
|
F1ABCDEF0123456789AB0001 /* CoachHealthRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1ABCDEF0123456789AB0002 /* CoachHealthRoute.swift */; };
|
||||||
A99E51A99E51A99E51A90001 /* CoachAppleAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = A99E51A99E51A99E51A90002 /* CoachAppleAuth.swift */; };
|
A99E51A99E51A99E51A90001 /* CoachAppleAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = A99E51A99E51A99E51A90002 /* CoachAppleAuth.swift */; };
|
||||||
|
60061E60061E60061E060001 /* CoachGoogleAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60061E60061E60061E060002 /* CoachGoogleAuth.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -117,6 +118,7 @@
|
|||||||
EBBF61A35F291A021BA75C60 /* CoachLiveActivityWidget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CoachLiveActivityWidget.swift; sourceTree = "<group>"; };
|
EBBF61A35F291A021BA75C60 /* CoachLiveActivityWidget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CoachLiveActivityWidget.swift; sourceTree = "<group>"; };
|
||||||
F1ABCDEF0123456789AB0002 /* CoachHealthRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachHealthRoute.swift; sourceTree = "<group>"; };
|
F1ABCDEF0123456789AB0002 /* CoachHealthRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachHealthRoute.swift; sourceTree = "<group>"; };
|
||||||
A99E51A99E51A99E51A90002 /* CoachAppleAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachAppleAuth.swift; sourceTree = "<group>"; };
|
A99E51A99E51A99E51A90002 /* CoachAppleAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachAppleAuth.swift; sourceTree = "<group>"; };
|
||||||
|
60061E60061E60061E060002 /* CoachGoogleAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachGoogleAuth.swift; sourceTree = "<group>"; };
|
||||||
F6BEAEF6F6274D61CAF22636 /* CoachWatchApp.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CoachWatchApp.swift; sourceTree = "<group>"; };
|
F6BEAEF6F6274D61CAF22636 /* CoachWatchApp.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CoachWatchApp.swift; sourceTree = "<group>"; };
|
||||||
FBFCA969A1B083CD3178D9B2 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
FBFCA969A1B083CD3178D9B2 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
@@ -202,6 +204,7 @@
|
|||||||
D1BD5B1990CF3F2B9C5D0002 /* CoachWorkoutKit.swift */,
|
D1BD5B1990CF3F2B9C5D0002 /* CoachWorkoutKit.swift */,
|
||||||
F1ABCDEF0123456789AB0002 /* CoachHealthRoute.swift */,
|
F1ABCDEF0123456789AB0002 /* CoachHealthRoute.swift */,
|
||||||
A99E51A99E51A99E51A90002 /* CoachAppleAuth.swift */,
|
A99E51A99E51A99E51A90002 /* CoachAppleAuth.swift */,
|
||||||
|
60061E60061E60061E060002 /* CoachGoogleAuth.swift */,
|
||||||
E2CE6C2AA1DF4F3CAD6E0002 /* MainViewController.swift */,
|
E2CE6C2AA1DF4F3CAD6E0002 /* MainViewController.swift */,
|
||||||
504EC30B1FED79650016851F /* Main.storyboard */,
|
504EC30B1FED79650016851F /* Main.storyboard */,
|
||||||
504EC30E1FED79650016851F /* Assets.xcassets */,
|
504EC30E1FED79650016851F /* Assets.xcassets */,
|
||||||
@@ -400,6 +403,7 @@
|
|||||||
D1BD5B1990CF3F2B9C5D0001 /* CoachWorkoutKit.swift in Sources */,
|
D1BD5B1990CF3F2B9C5D0001 /* CoachWorkoutKit.swift in Sources */,
|
||||||
F1ABCDEF0123456789AB0001 /* CoachHealthRoute.swift in Sources */,
|
F1ABCDEF0123456789AB0001 /* CoachHealthRoute.swift in Sources */,
|
||||||
A99E51A99E51A99E51A90001 /* CoachAppleAuth.swift in Sources */,
|
A99E51A99E51A99E51A90001 /* CoachAppleAuth.swift in Sources */,
|
||||||
|
60061E60061E60061E060001 /* CoachGoogleAuth.swift in Sources */,
|
||||||
88F28EB12FBB330E00E8306E /* CoachWorkoutObserver.swift in Sources */,
|
88F28EB12FBB330E00E8306E /* CoachWorkoutObserver.swift in Sources */,
|
||||||
E2CE6C2AA1DF4F3CAD6E0001 /* MainViewController.swift in Sources */,
|
E2CE6C2AA1DF4F3CAD6E0001 /* MainViewController.swift in Sources */,
|
||||||
21B242F8E61FB07C476C477C /* CoachLiveBridge.swift in Sources */,
|
21B242F8E61FB07C476C477C /* CoachLiveBridge.swift in Sources */,
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Capacitor
|
import Capacitor
|
||||||
|
#if canImport(GoogleSignIn)
|
||||||
|
import GoogleSignIn
|
||||||
|
#endif
|
||||||
|
|
||||||
@UIApplicationMain
|
@UIApplicationMain
|
||||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
@@ -51,6 +54,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
func applicationWillTerminate(_ application: UIApplication) {}
|
func applicationWillTerminate(_ application: UIApplication) {}
|
||||||
|
|
||||||
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
||||||
|
#if canImport(GoogleSignIn)
|
||||||
|
// Retour du flux OAuth Google (reversed client id URL scheme).
|
||||||
|
if GIDSignIn.sharedInstance.handle(url) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
|
return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
76
ios/App/App/CoachGoogleAuth.swift
Normal file
76
ios/App/App/CoachGoogleAuth.swift
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
// CoachGoogleAuth.swift
|
||||||
|
// Plugin Capacitor natif : Sign in with Google via le SDK GoogleSignIn-iOS.
|
||||||
|
//
|
||||||
|
// Usage côté JS (WebView) :
|
||||||
|
// const r = await window.Capacitor.Plugins.CoachGoogleAuth.signIn();
|
||||||
|
// → { idToken, email?, givenName?, familyName? }
|
||||||
|
// → reject("canceled") si annulé, sinon reject(message)
|
||||||
|
//
|
||||||
|
// L'`idToken` (JWT OIDC Google, aud = client iOS) est POSTé sur /auth/google/web
|
||||||
|
// côté backend qui le vérifie contre le JWKS Google (GOOGLE_CLIENT_IDS doit
|
||||||
|
// inclure le client iOS) puis pose le cookie de session.
|
||||||
|
//
|
||||||
|
// Prérequis Xcode (cf. HANDOFF) :
|
||||||
|
// - SDK GoogleSignIn ajouté via SPM (https://github.com/google/GoogleSignIn-iOS)
|
||||||
|
// - GIDClientID + URL scheme (reversed client id) dans Info.plist
|
||||||
|
// - AppDelegate.application(_:open:) appelle GIDSignIn.sharedInstance.handle(url)
|
||||||
|
//
|
||||||
|
// Tant que le SDK n'est pas bundlé, le plugin compile (garde canImport) mais
|
||||||
|
// signIn() rejette proprement.
|
||||||
|
import Foundation
|
||||||
|
import Capacitor
|
||||||
|
#if canImport(GoogleSignIn)
|
||||||
|
import GoogleSignIn
|
||||||
|
#endif
|
||||||
|
|
||||||
|
@objc(CoachGoogleAuthPlugin)
|
||||||
|
public class CoachGoogleAuthPlugin: CAPPlugin, CAPBridgedPlugin {
|
||||||
|
public let identifier = "CoachGoogleAuthPlugin"
|
||||||
|
public let jsName = "CoachGoogleAuth"
|
||||||
|
public let pluginMethods: [CAPPluginMethod] = [
|
||||||
|
CAPPluginMethod(name: "isAvailable", returnType: CAPPluginReturnPromise),
|
||||||
|
CAPPluginMethod(name: "signIn", returnType: CAPPluginReturnPromise),
|
||||||
|
]
|
||||||
|
|
||||||
|
@objc func isAvailable(_ call: CAPPluginCall) {
|
||||||
|
#if canImport(GoogleSignIn)
|
||||||
|
call.resolve(["available": true])
|
||||||
|
#else
|
||||||
|
call.resolve(["available": false, "reason": "GoogleSignIn SDK not bundled"])
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func signIn(_ call: CAPPluginCall) {
|
||||||
|
#if canImport(GoogleSignIn)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
guard let vc = self.bridge?.viewController else {
|
||||||
|
call.reject("No presenting view controller")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
GIDSignIn.sharedInstance.signIn(withPresenting: vc) { result, error in
|
||||||
|
if let error = error {
|
||||||
|
let nsErr = error as NSError
|
||||||
|
if nsErr.code == GIDSignInError.canceled.rawValue {
|
||||||
|
call.reject("canceled")
|
||||||
|
} else {
|
||||||
|
call.reject("Google sign-in failed: \(error.localizedDescription)")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let user = result?.user,
|
||||||
|
let idToken = user.idToken?.tokenString else {
|
||||||
|
call.reject("No id token returned by Google")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var data: [String: Any] = ["idToken": idToken]
|
||||||
|
if let email = user.profile?.email { data["email"] = email }
|
||||||
|
if let given = user.profile?.givenName { data["givenName"] = given }
|
||||||
|
if let family = user.profile?.familyName { data["familyName"] = family }
|
||||||
|
call.resolve(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
call.reject("GoogleSignIn SDK not bundled in this build")
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,9 @@ class MainViewController: CAPBridgeViewController {
|
|||||||
// Plugin natif Sign in with Apple — signIn() natif (AuthenticationServices)
|
// Plugin natif Sign in with Apple — signIn() natif (AuthenticationServices)
|
||||||
// → identity_token POSTé sur /auth/apple/web (cf. CoachAppleAuth.swift).
|
// → identity_token POSTé sur /auth/apple/web (cf. CoachAppleAuth.swift).
|
||||||
bridge?.registerPluginInstance(CoachAppleAuthPlugin())
|
bridge?.registerPluginInstance(CoachAppleAuthPlugin())
|
||||||
|
// Plugin natif Sign in with Google — signIn() via SDK GoogleSignIn
|
||||||
|
// → id_token POSTé sur /auth/google/web (cf. CoachGoogleAuth.swift).
|
||||||
|
bridge?.registerPluginInstance(CoachGoogleAuthPlugin())
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
|
|||||||
Reference in New Issue
Block a user