web notifications

This commit is contained in:
2025-10-08 09:25:45 +02:00
parent c1171ad9e9
commit dc652c1631
2 changed files with 176 additions and 59 deletions
+129 -31
View File
@@ -19,23 +19,34 @@ exports.sendNotificationWhenDocIsCreated = onDocumentCreated(
data: notifData = {}, data: notifData = {},
} = event.data.data(); } = event.data.data();
const { pushToken = null } =
(await db.collection(receiverCollection).doc(receiver).get())?.data() ||
{};
if (!receiver || !message) { if (!receiver || !message) {
throw new Error("Receiver and message are required"); throw new Error("Receiver and message are required");
} }
if (pushToken) { const userSnap = await db.collection(receiverCollection).doc(receiver).get();
await sendExpoNotification(pushToken, { const { pushToken = null, pushTokens = [] } = userSnap?.data() || {};
title: title || "MusicLand",
message: message, const tokensSet = new Set(
data: notifData || {}, []
}); .concat(Array.isArray(pushTokens) ? pushTokens : [])
} else { .concat(pushToken ? [pushToken] : [])
.filter(Boolean)
);
const tokens = Array.from(tokensSet);
if (!tokens?.length) {
console.warn("User push token not found"); console.warn("User push token not found");
return null;
} }
await sendExpoNotification({
tokens,
receiverId: receiver,
receiverCollection,
title: title || "MusicLand",
message: message,
data: notifData || {},
});
} catch (e) { } catch (e) {
console.log(e); console.log(e);
return e; return e;
@@ -44,29 +55,83 @@ exports.sendNotificationWhenDocIsCreated = onDocumentCreated(
); );
// Fonction pour envoyer la notification via Expo SDK // Fonction pour envoyer la notification via Expo SDK
async function sendExpoNotification( async function sendExpoNotification({
pushToken, tokens = [],
{ title = "", message = "", data = {} } title = "",
) { message = "",
data = {},
receiverId = null,
receiverCollection = "users",
}) {
try { try {
if (!Expo.isExpoPushToken(pushToken)) { const candidateTokens = Array.isArray(tokens) ? tokens : [tokens];
throw new Error(`Invalid push token: ${pushToken}`); const validTokens = [];
} else { const invalidTokens = [];
console.log("Valid push token:", pushToken);
candidateTokens.forEach((token) => {
if (Expo.isExpoPushToken(token)) {
validTokens.push(token);
} else if (token) {
invalidTokens.push(token);
}
});
if (invalidTokens.length && receiverId) {
await removeInvalidTokens({
tokens: invalidTokens,
receiverId,
receiverCollection,
});
}
if (!validTokens.length) {
console.warn("No valid Expo push tokens to send notification");
return { sent: false };
}
const messages = validTokens.map((token) => ({
to: token,
sound: "default",
title: title,
body: message,
data: data || {},
priority: "high",
badge: 1,
channelId: "default",
}));
const chunks = expo.chunkPushNotifications(messages);
const receipts = [];
const tokensToPrune = new Set();
for (const chunk of chunks) {
const chunkReceipts = await expo.sendPushNotificationsAsync(chunk);
chunkReceipts.forEach((receipt, index) => {
if (receipt?.status === "error") {
const errorCode = receipt?.details?.error || receipt?.details?.code;
console.log("Error sending notification:", receipt);
if (
errorCode === "DeviceNotRegistered" ||
errorCode === "PushTokenNotRegistered"
) {
const token = chunk[index]?.to;
if (token) {
tokensToPrune.add(token);
}
}
}
});
receipts.push(...chunkReceipts);
}
if (tokensToPrune.size && receiverId) {
await removeInvalidTokens({
tokens: Array.from(tokensToPrune),
receiverId,
receiverCollection,
});
} }
const receipts = await expo.sendPushNotificationsAsync([
{
to: pushToken,
sound: "default",
title: title,
body: message,
data: data || {},
priority: "high",
badge: 1,
channelId: "default",
},
]);
console.log("Sent push notifications:", receipts); console.log("Sent push notifications:", receipts);
return { sent: true }; return { sent: true };
@@ -76,6 +141,39 @@ async function sendExpoNotification(
} }
} }
async function removeInvalidTokens({ tokens = [], receiverId, receiverCollection }) {
try {
if (!receiverId || !tokens.length) {
return;
}
const uniqueTokens = Array.from(new Set(tokens.filter(Boolean)));
if (!uniqueTokens.length) {
return;
}
const docRef = db.collection(receiverCollection).doc(receiverId);
const userSnap = await docRef.get();
const userData = userSnap?.data() || {};
const updates = {
pushTokens: admin.firestore.FieldValue.arrayRemove(...uniqueTokens),
};
if (uniqueTokens.includes(userData?.pushToken)) {
updates.pushToken = admin.firestore.FieldValue.delete();
}
await docRef.set(updates, { merge: true });
console.log(
"Pruned invalid push tokens",
JSON.stringify({ receiverId, tokens: uniqueTokens }, null, 2)
);
} catch (error) {
console.log("Failed to prune invalid push tokens:", error);
}
}
// Fonction pour ajouter une notification à la base de données // Fonction pour ajouter une notification à la base de données
const sendNotification = async ({ const sendNotification = async ({
sender = "SYSTEM", sender = "SYSTEM",
+47 -28
View File
@@ -9,8 +9,10 @@ import React, {
useRef, useRef,
useState, useState,
} from "react"; } from "react";
import { Platform } from "react-native";
import { useGlobal } from "reactn"; import { useGlobal } from "reactn";
import firebase, { import firebase, {
arrayUnion,
notificationsRef, notificationsRef,
serverTimestamp, serverTimestamp,
usersRef, usersRef,
@@ -152,35 +154,52 @@ export default function NotificationProvider({ children }) {
async function registerForPushNotificationsAsync() { async function registerForPushNotificationsAsync() {
try { try {
if (Device.isDevice) { const isWeb = Platform.OS === "web";
let { status } = await Notifications.getPermissionsAsync(); if (!Device.isDevice && !isWeb) {
if (status !== "granted") {
const request = await Notifications.requestPermissionsAsync();
status = request.status;
}
if (status !== "granted") {
console.log("Notification permissions denied");
setAllowNotifications(false);
return;
}
setAllowNotifications(true);
// Get the push token
const pushToken = (
await Notifications.getExpoPushTokenAsync({
projectId: Constants.expoConfig?.extra?.eas?.projectId || "",
})
).data;
if (user?.pushToken !== pushToken && !!user) {
await usersRef.doc(uid).update({ pushToken });
// if (!user?.alerts) {
// await usersRef.doc(uid).update({
// alerts: Object.values(ALERT_TYPE),
// });
// }
}
} else {
console.log("Must use physical device for Push Notifications"); console.log("Must use physical device for Push Notifications");
setAllowNotifications(false);
setNotifInit(true);
return;
}
let { status } = await Notifications.getPermissionsAsync();
if (status !== "granted") {
const request = await Notifications.requestPermissionsAsync();
status = request.status;
}
if (status !== "granted") {
console.log("Notification permissions denied");
setAllowNotifications(false);
return;
}
setAllowNotifications(true);
const projectId =
Constants.expoConfig?.extra?.eas?.projectId ||
Constants.manifest2?.extra?.eas?.projectId ||
Constants.manifest?.extra?.eas?.projectId ||
"";
const pushToken = (
await Notifications.getExpoPushTokenAsync({
projectId,
})
)?.data;
if (!pushToken) {
console.log("No push token retrieved");
return;
}
if (!!user && pushToken) {
await usersRef.doc(uid).set(
{
pushTokens: arrayUnion(pushToken),
},
{ merge: true }
);
} }
} catch (e) { } catch (e) {
console.log(e); console.log(e);