Gustavo Zanatta 1 săptămână în urmă
părinte
comite
a212464172
2 a modificat fișierele cu 75 adăugiri și 18 ștergeri
  1. 27 6
      src/boot/push-notifications.js
  2. 48 12
      src/composables/usePushNotifications.js

+ 27 - 6
src/boot/push-notifications.js

@@ -1,10 +1,13 @@
 import { defineBoot } from "#q-app/wrappers";
 import { Capacitor } from "@capacitor/core";
+import { Notify } from "quasar";
 import { PushNotifications } from "@capacitor/push-notifications";
 import { useAuth } from "src/composables/useAuth";
 import { usePushNotifications } from "src/composables/usePushNotifications";
 
-export default defineBoot(({ router }) => {
+const CHANNEL_ID = "diaria";
+
+export default defineBoot(async ({ router }) => {
   if (!Capacitor.isNativePlatform()) {
     return;
   }
@@ -12,11 +15,20 @@ export default defineBoot(({ router }) => {
   const { requestPermissionAndRegister, setDeviceToken, syncDeviceToken } =
     usePushNotifications();
 
+  if (Capacitor.getPlatform() === "android") {
+    await PushNotifications.createChannel({
+      id: CHANNEL_ID,
+      name: "Notificações",
+      description: "Canal padrão de notificações",
+      importance: 4, // HIGH
+      visibility: 1, // PUBLIC
+      vibration: true,
+    }).catch(() => {});
+  }
+
   PushNotifications.addListener("registration", async (token) => {
-    setDeviceToken(token.value);
+    await setDeviceToken(token.value);
 
-    // Se o app abriu já autenticado, envia agora; caso contrário o envio
-    // acontece no login (useAuth), quando o access token existir.
     await syncDeviceToken();
   });
 
@@ -24,8 +36,17 @@ export default defineBoot(({ router }) => {
     // falha silenciosa
   });
 
-  // Ao tocar em uma push, reconsulta a aprovação: se o cadastro acabou de ser
-  // aprovado, a dashboard completa é liberada sem exigir novo login.
+  PushNotifications.addListener("pushNotificationReceived", (notification) => {
+    Notify.create({
+      message: notification.title || "Nova notificação",
+      caption: notification.body,
+      type: "info",
+      position: "top",
+      timeout: 6000,
+      actions: [{ icon: "close", color: "white", round: true }],
+    });
+  });
+
   PushNotifications.addListener("pushNotificationActionPerformed", async () => {
     try {
       const approved = await useAuth().refreshApprovalStatus();

+ 48 - 12
src/composables/usePushNotifications.js

@@ -1,33 +1,62 @@
 import { Capacitor } from "@capacitor/core";
+import { Preferences } from "@capacitor/preferences";
 import { PushNotifications } from "@capacitor/push-notifications";
 import { registerDeviceToken, removeDeviceToken } from "src/api/deviceToken";
 import { userStore } from "src/stores/user";
 
-// Token FCM do dispositivo, capturado pelo listener "registration" do boot.
-// Fica em escopo de módulo para continuar acessível no logout.
-let deviceToken = null;
+const PUSH_TOKEN_KEY = "fcm_device_token";
 
 export const usePushNotifications = () => {
   const isSupported = () => Capacitor.isNativePlatform();
 
-  const setDeviceToken = (token) => {
-    deviceToken = token;
+  const setDeviceToken = async (token) => {
+    if (!isSupported()) {
+      return;
+    }
+
+    await Preferences.set({
+      key: PUSH_TOKEN_KEY,
+      value: JSON.stringify({ token, platform: Capacitor.getPlatform() }),
+    });
+  };
+
+  const getDeviceToken = async () => {
+    if (!isSupported()) {
+      return null;
+    }
+
+    const { value } = await Preferences.get({ key: PUSH_TOKEN_KEY });
+
+    if (!value) {
+      return null;
+    }
+
+    try {
+      return JSON.parse(value);
+    } catch {
+      return null;
+    }
   };
 
   /**
    * Envia o token do dispositivo para a API.
-   * Só faz sentido autenticado: sem access token o POST volta 401, o
-   * dispositivo fica sem push e o interceptor ainda tenta um refresh à toa.
+   * O token continua guardado depois do envio — o logout precisa dele.
    */
   const syncDeviceToken = async () => {
-    if (!isSupported() || !deviceToken || !userStore().accessToken) {
+    if (!isSupported() || !userStore().accessToken) {
+      return;
+    }
+
+    const stored = await getDeviceToken();
+
+    if (!stored?.token) {
       return;
     }
 
     try {
-      await registerDeviceToken(deviceToken, Capacitor.getPlatform());
+      await registerDeviceToken(stored.token, stored.platform);
     } catch {
-      // falha silenciosa — não bloqueia o uso do app
+      // falha silenciosa — o token segue guardado para a próxima tentativa
     }
   };
 
@@ -36,12 +65,18 @@ export const usePushNotifications = () => {
    * depois que o usuário sai da conta.
    */
   const unregisterDeviceToken = async () => {
-    if (!isSupported() || !deviceToken || !userStore().accessToken) {
+    if (!isSupported() || !userStore().accessToken) {
+      return;
+    }
+
+    const stored = await getDeviceToken();
+
+    if (!stored?.token) {
       return;
     }
 
     try {
-      await removeDeviceToken(deviceToken);
+      await removeDeviceToken(stored.token);
     } catch {
       // falha silenciosa — não pode impedir o logout
     }
@@ -73,6 +108,7 @@ export const usePushNotifications = () => {
     isSupported,
     requestPermissionAndRegister,
     setDeviceToken,
+    getDeviceToken,
     syncDeviceToken,
     unregisterDeviceToken,
   };