# Flutter Agent Prompt — Firebase (social login + push)

Paste the block below to your Flutter agent. It's self-contained; the agent won't need the backend
repo. It assumes the app already has the Dio `ApiClient` / `TokenStore` / `ApiException` layer from
the API integration guide. Prerequisites in the Firebase console are in `FIREBASE_SETUP.md`
(Parts A–D must be done: providers enabled, `google-services.json` / `GoogleService-Info.plist` added,
APNs key for iOS push).

---

```
TASK: Wire Firebase into the Flutter app for (1) Google/Apple social login and (2) FCM push
notifications, integrating with our existing REST backend. Build on the app's existing Dio
ApiClient / TokenStore / ApiException layer — do not reinvent networking or token storage.

CONTEXT — how auth works in this app (do not change these rules):
- The backend issues its OWN JWTs (accessToken + refreshToken). The app always calls protected
  APIs with `Authorization: Bearer <backendAccessToken>`.
- Firebase is ONLY used to sign the user in with Google/Apple and to receive push. A Firebase
  token is sent to the backend exactly once, at the social-login exchange below. Never attach a
  Firebase token to any other API call.

=== PART 1: SOCIAL LOGIN (Google + Apple) ===

Packages: firebase_core, firebase_auth, google_sign_in, sign_in_with_apple.
Init: call Firebase.initializeApp() in main() before runApp.

Flow:
  1. User taps "Continue with Google" (or Apple).
  2. Run the Firebase sign-in for that provider (google_sign_in → credential → 
     FirebaseAuth.instance.signInWithCredential(...); Apple similarly).
  3. Get the Firebase ID token:  final idToken = await FirebaseAuth.instance.currentUser!.getIdToken();
  4. Exchange it with OUR backend:

     POST {baseUrl}/api/auth/social
     Auth: none (this is a public endpoint)
     Body (JSON): { "idToken": "<firebase-id-token>" }

     SUCCESS 200, envelope:
     {
       "data": {
         "user": { "id","email","name","role","emailVerified","createdAt","updatedAt", ... },
         "accessToken": "<backend JWT>",
         "refreshToken": "<backend JWT>"
       },
       "error": null
     }
     ERROR envelope: { "data": null, "error": { "code": "...", "message": "..." } }
       - 401 UNAUTHENTICATED — invalid/expired Firebase token
       - 422 VALIDATION_ERROR — malformed body

  5. Save data.accessToken + data.refreshToken via the existing TokenStore (exactly like
     email/password login already does), set the current user from data.user, route into the app.

Add to the existing AuthService:
  Future<AuthResult> loginWithGoogle();   // does steps 2–5
  Future<AuthResult> loginWithApple();    // does steps 2–5
Reuse the SAME token-persistence and post-login navigation as the existing login()/register().

Sign-out: sign out of Firebase (FirebaseAuth.instance.signOut(), GoogleSignIn().signOut()) AND
call the existing backend logout (clears backend tokens from TokenStore).

Notes:
- Apple sign-in requires an Apple Developer account + capability; guard the Apple button so it only
  shows on iOS (and web if configured).
- Handle user-cancelled sign-in silently (no error dialog).

=== PART 2: PUSH NOTIFICATIONS (FCM) ===

Package: firebase_messaging.

On app start (after the user is logged in) and whenever the token refreshes:
  1. Request notification permission (iOS/web): FirebaseMessaging.instance.requestPermission().
  2. Get the device token: final fcm = await FirebaseMessaging.instance.getToken();
  3. Register it with OUR backend:

     POST {baseUrl}/api/devices
     Auth: REQUIRED (Bearer backend accessToken — the existing interceptor handles it)
     Body (JSON): { "token": "<fcm-device-token>", "platform": "android" | "ios" | "web" }
     Success: 200/201 envelope with the saved device.

  4. Listen for token rotation: FirebaseMessaging.instance.onTokenRefresh → re-POST /api/devices.
  5. On logout, unregister:  DELETE {baseUrl}/api/devices/<fcm-device-token>  (Bearer required).

Handle incoming messages — foreground (onMessage), background (onBackgroundMessage), and tap
(onMessageOpenedApp). Our pushes are DATA messages. IMPORTANT special case:

  If a received message's data contains  { "action": "REFRESH_TOKEN" }:
    → call the backend refresh flow (POST /api/auth/refresh with the stored refreshToken; the
      existing ApiClient already knows how), then re-fetch GET /api/users/me.
    This is how a role change (e.g. client → business after a partner approval) reaches the app:
    the new access token carries the updated role. Do NOT show a visible notification for this one.

Add a small DeviceService (or fold into an existing notifications service):
  Future<void> registerCurrentDevice();     // steps 1–3
  Future<void> unregisterCurrentDevice();    // step 5
  void startTokenRefreshListener();          // step 4
  void handleDataMessage(Map<String,dynamic> data); // includes the REFRESH_TOKEN case

=== ACCEPTANCE CRITERIA ===
- Google login: tapping the button signs in via Firebase, exchanges the ID token at /api/auth/social,
  stores the backend tokens, and lands the user in the app authenticated.
- Apple login works on iOS (button hidden elsewhere).
- After login, the FCM token is registered via POST /api/devices; rotating the token re-registers it;
  logout unregisters it and clears Firebase + backend sessions.
- A DATA push with action=REFRESH_TOKEN silently refreshes the access token and re-loads /users/me
  (role updates without the user logging out/in).
- No Firebase token is ever attached to any API call except the one-time /api/auth/social exchange.
- All errors map through the existing ApiException so messages surface in the UI.

Assume Firebase console setup (providers enabled, google-services.json / GoogleService-Info.plist
present, APNs key for iOS) is already done.
```
