Gustavo Zanatta hai 1 semana
pai
achega
c1ddf77e09

+ 33 - 46
src/boot/push-notifications.js

@@ -1,65 +1,52 @@
 import { defineBoot } from "#q-app/wrappers";
 import { Capacitor } from "@capacitor/core";
+import { Notify } from "quasar";
 import { PushNotifications } from "@capacitor/push-notifications";
-import { Preferences } from "@capacitor/preferences";
-import { registerDeviceToken } from "src/api/deviceToken";
+import { usePushNotifications } from "src/composables/usePushNotifications";
 
-const FCM_TOKEN_KEY = "pending_fcm_token";
-
-export const registerPendingFcmToken = async () => {
-  if (!Capacitor.isNativePlatform()) return;
-
-  const { value } = await Preferences.get({ key: FCM_TOKEN_KEY });
-  if (!value) return;
-
-  try {
-    const { token, platform } = JSON.parse(value);
-    await registerDeviceToken(token, platform);
-    await Preferences.remove({ key: FCM_TOKEN_KEY });
-  } catch {
-    // mantém o token pendente para tentar na próxima autenticação
-  }
-};
-
-const setupPushNotifications = async () => {
-  let permission = await PushNotifications.checkPermissions();
-
-  if (permission.receive === "prompt") {
-    permission = await PushNotifications.requestPermissions();
-  }
-
-  if (permission.receive !== "granted") {
-    return;
-  }
-
-  await PushNotifications.register();
-};
+const CHANNEL_ID = "diaria";
 
 export default defineBoot(async () => {
   if (!Capacitor.isNativePlatform()) {
     return;
   }
 
-  await PushNotifications.createChannel({
-    id: "default",
-    name: "Notificações",
-    description: "Canal padrão de notificações",
-    importance: 4, // HIGH
-    visibility: 1, // PUBLIC
-    vibration: true,
-  }).catch(() => {});
+  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(() => {});
+
+    await PushNotifications.deleteChannel({ id: "default" }).catch(() => {});
+  }
 
   PushNotifications.addListener("registration", async (token) => {
-    const platform = Capacitor.getPlatform();
-    await Preferences.set({
-      key: FCM_TOKEN_KEY,
-      value: JSON.stringify({ token: token.value, platform }),
-    });
+    await setDeviceToken(token.value);
+
+    await syncDeviceToken();
   });
 
   PushNotifications.addListener("registrationError", () => {
     // falha silenciosa
   });
 
-  setupPushNotifications();
+  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 }],
+    });
+  });
+
+  requestPermissionAndRegister();
 });

+ 5 - 2
src/composables/useAuth.js

@@ -2,7 +2,7 @@ import api from "src/api";
 import { permissionStore } from "src/stores/permission";
 import { userStore } from "src/stores/user";
 import { useAuthStorage } from "src/composables/useAuthStorage";
-import { registerPendingFcmToken } from "src/boot/push-notifications";
+import { usePushNotifications } from "src/composables/usePushNotifications";
 
 let refreshPromise = null;
 
@@ -21,7 +21,7 @@ export const useAuth = () => {
     userStore().accessToken = access_token;
     await setRefreshToken(refresh_token);
     await permissionStore().fetchScopes();
-    await registerPendingFcmToken();
+    await usePushNotifications().syncDeviceToken();
   };
 
   const login = async (email, password) => {
@@ -42,6 +42,9 @@ export const useAuth = () => {
 
   const logout = async () => {
     try {
+      // Precisa acontecer antes do /logout, enquanto o token ainda é válido.
+      await usePushNotifications().unregisterDeviceToken();
+
       const response = await api.post("/logout");
       if (response.status === 200) {
         await clearAuthData();

+ 120 - 0
src/composables/usePushNotifications.js

@@ -0,0 +1,120 @@
+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 persistido em disco: o listener "registration" quase sempre dispara
+// antes de existir sessão, e guardar só em memória faz quem instala e loga
+// depois de reabrir o app nunca registrar o dispositivo na API.
+const PUSH_TOKEN_KEY = "fcm_device_token";
+
+export const usePushNotifications = () => {
+  const isSupported = () => Capacitor.isNativePlatform();
+
+  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() || !userStore().accessToken) {
+      return;
+    }
+
+    const stored = await getDeviceToken();
+
+    if (!stored?.token) {
+      return;
+    }
+
+    try {
+      await registerDeviceToken(stored.token, stored.platform);
+    } catch {
+      // falha silenciosa — o token segue guardado para a próxima tentativa
+    }
+  };
+
+  /**
+   * Desativa o token na API para o dispositivo parar de receber push
+   * depois que o usuário sai da conta.
+   */
+  const unregisterDeviceToken = async () => {
+    if (!isSupported() || !userStore().accessToken) {
+      return;
+    }
+
+    const stored = await getDeviceToken();
+
+    if (!stored?.token) {
+      return;
+    }
+
+    try {
+      await removeDeviceToken(stored.token);
+    } catch {
+      // falha silenciosa — não pode impedir o logout
+    }
+  };
+
+  /**
+   * Solicita a permissão de notificações e registra o app no FCM,
+   * o que dispara o listener "registration" configurado no boot.
+   */
+  const requestPermissionAndRegister = async () => {
+    if (!isSupported()) {
+      return;
+    }
+
+    let permission = await PushNotifications.checkPermissions();
+
+    if (permission.receive === "prompt") {
+      permission = await PushNotifications.requestPermissions();
+    }
+
+    if (permission.receive !== "granted") {
+      return;
+    }
+
+    await PushNotifications.register();
+  };
+
+  return {
+    isSupported,
+    requestPermissionAndRegister,
+    setDeviceToken,
+    getDeviceToken,
+    syncDeviceToken,
+    unregisterDeviceToken,
+  };
+};