feat(notifications): register FCM tokens with backend
Add registerToken POST to /notifications/register (platform, testMode). Call it from Capacitor registration and Firebase getToken with deduped registerRetrievedToken; expose registerToken via barrel and useNotifications as registerFcmToken.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import { inject } from "vue";
|
import { inject } from "vue";
|
||||||
import { NotificationIface } from "../constants/app";
|
import { NotificationIface } from "../constants/app";
|
||||||
|
import { registerToken } from "@/services/notifications/NotificationService";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vue 3 composable for notifications
|
* Vue 3 composable for notifications
|
||||||
@@ -93,5 +94,7 @@ export function useNotifications() {
|
|||||||
notAGive,
|
notAGive,
|
||||||
notificationOff,
|
notificationOff,
|
||||||
downloadStarted,
|
downloadStarted,
|
||||||
|
/** POST FCM token to `/notifications/register` (same as startup native hook). */
|
||||||
|
registerFcmToken: registerToken,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,32 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Capacitor } from "@capacitor/core";
|
import { Capacitor } from "@capacitor/core";
|
||||||
|
import { logger } from "@/utils/logger";
|
||||||
import { NativeNotificationService } from "./NativeNotificationService";
|
import { NativeNotificationService } from "./NativeNotificationService";
|
||||||
import { WebPushNotificationService } from "./WebPushNotificationService";
|
import { WebPushNotificationService } from "./WebPushNotificationService";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers an FCM device token with the app backend (native Capacitor token or web getToken).
|
||||||
|
*/
|
||||||
|
export async function registerToken(fcmToken: string): Promise<void> {
|
||||||
|
const res = await fetch("/notifications/register", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
fcmToken,
|
||||||
|
platform: Capacitor.getPlatform(),
|
||||||
|
testMode: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
logger.warn("[NotificationService] registerToken failed", {
|
||||||
|
status: res.status,
|
||||||
|
statusText: res.statusText,
|
||||||
|
});
|
||||||
|
throw new Error(`registerToken failed: HTTP ${res.status}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for scheduling a daily notification
|
* Options for scheduling a daily notification
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -21,11 +21,26 @@ import {
|
|||||||
onMessage,
|
onMessage,
|
||||||
} from "firebase/messaging";
|
} from "firebase/messaging";
|
||||||
import { logger } from "@/utils/logger";
|
import { logger } from "@/utils/logger";
|
||||||
|
import { registerToken } from "./NotificationService";
|
||||||
|
|
||||||
const LOG = "[FirebaseMessaging]";
|
const LOG = "[FirebaseMessaging]";
|
||||||
|
|
||||||
let firebaseAppSingleton: FirebaseApp | null = null;
|
let firebaseAppSingleton: FirebaseApp | null = null;
|
||||||
let nativeInitPromise: Promise<void> | null = null;
|
let nativeInitPromise: Promise<void> | null = null;
|
||||||
|
/** Avoid duplicate POSTs when the same token is delivered more than once. */
|
||||||
|
let lastRegisteredFcmToken: string | null = null;
|
||||||
|
|
||||||
|
async function registerRetrievedToken(token: string): Promise<void> {
|
||||||
|
const trimmed = token.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (trimmed === lastRegisteredFcmToken) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await registerToken(trimmed);
|
||||||
|
lastRegisteredFcmToken = trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
function readFirebaseOptions(): FirebaseOptions | null {
|
function readFirebaseOptions(): FirebaseOptions | null {
|
||||||
const env = import.meta.env;
|
const env = import.meta.env;
|
||||||
@@ -107,6 +122,7 @@ async function attachFirebaseMessagingIfSupported(
|
|||||||
logger.info(`${LOG} Firebase getToken completed`, {
|
logger.info(`${LOG} Firebase getToken completed`, {
|
||||||
tokenPrefix: token ? `${token.slice(0, 12)}…` : "(empty)",
|
tokenPrefix: token ? `${token.slice(0, 12)}…` : "(empty)",
|
||||||
});
|
});
|
||||||
|
await registerRetrievedToken(token);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
`${LOG} Firebase getToken failed (common on native WebView without SW)`,
|
`${LOG} Firebase getToken failed (common on native WebView without SW)`,
|
||||||
@@ -135,6 +151,12 @@ async function initializeNativePushAndFirebaseMessagingImpl(): Promise<void> {
|
|||||||
logger.info(`${LOG} Capacitor registration token`, {
|
logger.info(`${LOG} Capacitor registration token`, {
|
||||||
valuePrefix: token.value ? `${token.value.slice(0, 12)}…` : "(empty)",
|
valuePrefix: token.value ? `${token.value.slice(0, 12)}…` : "(empty)",
|
||||||
});
|
});
|
||||||
|
void registerRetrievedToken(token.value).catch((err) => {
|
||||||
|
logger.warn(
|
||||||
|
`${LOG} registerToken after Capacitor registration failed`,
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
await PushNotifications.addListener("registrationError", (err) => {
|
await PushNotifications.addListener("registrationError", (err) => {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { NotificationService } from "./NotificationService";
|
export { NotificationService, registerToken } from "./NotificationService";
|
||||||
export { NativeNotificationService } from "./NativeNotificationService";
|
export { NativeNotificationService } from "./NativeNotificationService";
|
||||||
export { WebPushNotificationService } from "./WebPushNotificationService";
|
export { WebPushNotificationService } from "./WebPushNotificationService";
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user