mirror of
https://github.com/urosran/cally.git
synced 2025-07-10 15:17:17 +00:00
Syncing logic improvements... Notification logic refactor (stop spamming notifications on sync)
This commit is contained in:
@ -94,7 +94,7 @@ const CalendarSettingsPage = (props: {
|
|||||||
newUserData: {googleToken: accessToken, googleMail: googleMail},
|
newUserData: {googleToken: accessToken, googleMail: googleMail},
|
||||||
});
|
});
|
||||||
|
|
||||||
await fetchAndSaveGoogleEvents(accessToken, googleMail)
|
await fetchAndSaveGoogleEvents({token: accessToken, email: googleMail})
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error during Google sign-in:", error);
|
console.error("Error during Google sign-in:", error);
|
||||||
@ -207,12 +207,12 @@ const CalendarSettingsPage = (props: {
|
|||||||
if (appleToken) {
|
if (appleToken) {
|
||||||
console.log("Apple ID token received. Fetch user info if needed...");
|
console.log("Apple ID token received. Fetch user info if needed...");
|
||||||
|
|
||||||
// Example: Store user token and email
|
|
||||||
await updateUserData({
|
await updateUserData({
|
||||||
newUserData: {appleToken, appleMail},
|
newUserData: {appleToken, appleMail},
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("User data updated with Apple ID token.");
|
console.log("User data updated with Apple ID token.");
|
||||||
|
await fetchAndSaveAppleEvents({token: appleToken, email: appleMail!});
|
||||||
} else {
|
} else {
|
||||||
console.warn("Apple authentication was not successful or email was hidden.");
|
console.warn("Apple authentication was not successful or email was hidden.");
|
||||||
}
|
}
|
||||||
@ -252,7 +252,7 @@ const CalendarSettingsPage = (props: {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChangeFirstDayOfWeek = () => {
|
const handleChangeFirstDayOfWeek = (firstDayOfWeek: string) => {
|
||||||
setFirstDayOfWeek(firstDayOfWeek === "Sundays" ? "Mondays" : "Sundays");
|
setFirstDayOfWeek(firstDayOfWeek === "Sundays" ? "Mondays" : "Sundays");
|
||||||
debouncedUpdateFirstDayOfWeek(firstDayOfWeek === "Sundays" ? "Mondays" : "Sundays");
|
debouncedUpdateFirstDayOfWeek(firstDayOfWeek === "Sundays" ? "Mondays" : "Sundays");
|
||||||
}
|
}
|
||||||
@ -436,12 +436,18 @@ const CalendarSettingsPage = (props: {
|
|||||||
<View style={{marginTop: 20}}>
|
<View style={{marginTop: 20}}>
|
||||||
{!!profileData?.googleMail && (
|
{!!profileData?.googleMail && (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => fetchAndSaveGoogleEvents(undefined, undefined)}
|
onPress={() => fetchAndSaveGoogleEvents({
|
||||||
|
token: profileData?.googleToken!,
|
||||||
|
email: profileData?.googleMail!
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<View row paddingR-20 center>
|
<View row paddingR-20 center>
|
||||||
<Button
|
<Button
|
||||||
disabled={isSyncingGoogle}
|
disabled={isSyncingGoogle}
|
||||||
onPress={() => fetchAndSaveGoogleEvents(undefined, undefined)}
|
onPress={() => fetchAndSaveGoogleEvents({
|
||||||
|
token: profileData?.googleToken!,
|
||||||
|
email: profileData?.googleMail!
|
||||||
|
})}
|
||||||
label={`Sync ${profileData?.googleMail}`}
|
label={`Sync ${profileData?.googleMail}`}
|
||||||
labelStyle={styles.addCalLbl}
|
labelStyle={styles.addCalLbl}
|
||||||
labelProps={{numberOfLines: 3}}
|
labelProps={{numberOfLines: 3}}
|
||||||
@ -466,11 +472,18 @@ const CalendarSettingsPage = (props: {
|
|||||||
|
|
||||||
|
|
||||||
{!!profileData?.appleMail && (
|
{!!profileData?.appleMail && (
|
||||||
<TouchableOpacity>
|
<TouchableOpacity
|
||||||
|
onPress={() => fetchAndSaveAppleEvents({
|
||||||
|
email: profileData?.appleMail!,
|
||||||
|
token: profileData?.appleToken!
|
||||||
|
})}>
|
||||||
<View row paddingR-20 center>
|
<View row paddingR-20 center>
|
||||||
<Button
|
<Button
|
||||||
disabled={isSyncingApple}
|
disabled={isSyncingApple}
|
||||||
onPress={() => fetchAndSaveAppleEvents(undefined, undefined)}
|
onPress={() => fetchAndSaveAppleEvents({
|
||||||
|
email: profileData?.appleMail!,
|
||||||
|
token: profileData?.appleToken!
|
||||||
|
})}
|
||||||
label={`Sync ${profileData?.appleMail}`}
|
label={`Sync ${profileData?.appleMail}`}
|
||||||
labelStyle={styles.addCalLbl}
|
labelStyle={styles.addCalLbl}
|
||||||
labelProps={{numberOfLines: 3}}
|
labelProps={{numberOfLines: 3}}
|
||||||
@ -494,12 +507,18 @@ const CalendarSettingsPage = (props: {
|
|||||||
|
|
||||||
{!!profileData?.outlookMail && (
|
{!!profileData?.outlookMail && (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => fetchAndSaveOutlookEvents(undefined, undefined)}
|
onPress={() => fetchAndSaveOutlookEvents({
|
||||||
|
token: profileData?.microsoftToken!,
|
||||||
|
email: profileData?.outlookMail!
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<View row paddingR-20 center>
|
<View row paddingR-20 center>
|
||||||
<Button
|
<Button
|
||||||
disabled={isSyncingOutlook}
|
disabled={isSyncingOutlook}
|
||||||
onPress={() => fetchAndSaveOutlookEvents(undefined, undefined)}
|
onPress={() => fetchAndSaveOutlookEvents({
|
||||||
|
token: profileData?.microsoftToken!,
|
||||||
|
email: profileData?.outlookMail!
|
||||||
|
})}
|
||||||
label={`Sync ${profileData?.outlookMail}`}
|
label={`Sync ${profileData?.outlookMail}`}
|
||||||
labelStyle={styles.addCalLbl}
|
labelStyle={styles.addCalLbl}
|
||||||
labelProps={{numberOfLines: 3}}
|
labelProps={{numberOfLines: 3}}
|
||||||
|
@ -9,101 +9,89 @@ const {Expo} = require('expo-server-sdk');
|
|||||||
admin.initializeApp();
|
admin.initializeApp();
|
||||||
const db = admin.firestore();
|
const db = admin.firestore();
|
||||||
|
|
||||||
let expo = new Expo({accessToken: process.env.EXPO_ACCESS_TOKEN});
|
let expo = new Expo({ accessToken: process.env.EXPO_ACCESS_TOKEN });
|
||||||
|
let notificationTimeout = null;
|
||||||
|
let eventCount = 0;
|
||||||
|
let pushTokens = [];
|
||||||
|
|
||||||
exports.sendNotificationOnEventCreation = functions.firestore
|
exports.sendNotificationOnEventCreation = functions.firestore
|
||||||
.document('Events/{eventId}')
|
.document('Events/{eventId}')
|
||||||
.onCreate(async (snapshot, context) => {
|
.onCreate(async (snapshot, context) => {
|
||||||
const eventData = snapshot.data();
|
const eventData = snapshot.data();
|
||||||
const { familyId, creatorId } = eventData;
|
const {familyId, creatorId} = eventData;
|
||||||
|
|
||||||
if (!familyId || !creatorId) {
|
if (!familyId || !creatorId) {
|
||||||
console.error('Missing familyId or creatorId in event data');
|
console.error('Missing familyId or creatorId in event data');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pushTokens = await getPushTokensForFamilyExcludingCreator(familyId, creatorId);
|
|
||||||
|
|
||||||
if (!pushTokens.length) {
|
if (!pushTokens.length) {
|
||||||
console.log('No push tokens available for the event.');
|
pushTokens = await getPushTokensForFamilyExcludingCreator(familyId, creatorId);
|
||||||
return;
|
if (!pushTokens.length) {
|
||||||
|
console.log('No push tokens available for the event.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let messages = [];
|
// Increment event count for debouncing
|
||||||
for (let pushToken of pushTokens) {
|
eventCount++;
|
||||||
if (!Expo.isExpoPushToken(pushToken)) {
|
|
||||||
console.error(`Push token ${pushToken} is not a valid Expo push token`);
|
if (notificationTimeout) {
|
||||||
continue;
|
clearTimeout(notificationTimeout); // Reset the timer if events keep coming
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a debounce time (e.g., 5 seconds)
|
||||||
|
notificationTimeout = setTimeout(async () => {
|
||||||
|
const eventMessage = eventCount === 1
|
||||||
|
? `An event "${eventData.title}" has been added. Check it out!`
|
||||||
|
: `${eventCount} new events have been added.`;
|
||||||
|
|
||||||
|
let messages = [];
|
||||||
|
for (let pushToken of pushTokens) {
|
||||||
|
if (!Expo.isExpoPushToken(pushToken)) {
|
||||||
|
console.error(`Push token ${pushToken} is not a valid Expo push token`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
messages.push({
|
||||||
|
to: pushToken,
|
||||||
|
sound: 'default',
|
||||||
|
title: 'New Events Added!',
|
||||||
|
body: eventMessage,
|
||||||
|
data: {eventId: context.params.eventId},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
messages.push({
|
let chunks = expo.chunkPushNotifications(messages);
|
||||||
to: pushToken,
|
let tickets = [];
|
||||||
sound: 'default',
|
|
||||||
title: 'New Event Added!',
|
|
||||||
body: `An event "${eventData.title}" has been added. Check it out!`,
|
|
||||||
data: { eventId: context.params.eventId },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let chunks = expo.chunkPushNotifications(messages);
|
for (let chunk of chunks) {
|
||||||
let tickets = [];
|
try {
|
||||||
for (let chunk of chunks) {
|
let ticketChunk = await expo.sendPushNotificationsAsync(chunk);
|
||||||
try {
|
tickets.push(...ticketChunk);
|
||||||
let ticketChunk = await expo.sendPushNotificationsAsync(chunk);
|
|
||||||
tickets.push(...ticketChunk);
|
|
||||||
|
|
||||||
for (let ticket of ticketChunk) {
|
for (let ticket of ticketChunk) {
|
||||||
if (ticket.status === 'ok') {
|
if (ticket.status === 'ok') {
|
||||||
console.log('Notification successfully sent:', ticket.id);
|
console.log('Notification successfully sent:', ticket.id);
|
||||||
} else if (ticket.status === 'error') {
|
} else if (ticket.status === 'error') {
|
||||||
console.error(`Notification error: ${ticket.message}`);
|
console.error(`Notification error: ${ticket.message}`);
|
||||||
if (ticket.details && ticket.details.error) {
|
if (ticket.details && ticket.details.error === 'DeviceNotRegistered') {
|
||||||
console.error('Error details:', ticket.details.error);
|
|
||||||
if (ticket.details.error === 'DeviceNotRegistered') {
|
|
||||||
console.log(`Removing invalid push token: ${ticket.to}`);
|
|
||||||
await removeInvalidPushToken(ticket.to);
|
await removeInvalidPushToken(ticket.to);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sending notification:', error);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error('Error sending notification:', error);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve and handle notification receipts
|
eventCount = 0; // Reset the event count after sending notification
|
||||||
let receiptIds = [];
|
pushTokens = []; // Reset push tokens for the next round
|
||||||
for (let ticket of tickets) {
|
|
||||||
if (ticket.id) {
|
|
||||||
receiptIds.push(ticket.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let receiptIdChunks = expo.chunkPushNotificationReceiptIds(receiptIds);
|
}, 5000); // Debounce time (5 seconds)
|
||||||
for (let chunk of receiptIdChunks) {
|
|
||||||
try {
|
|
||||||
let receipts = await expo.getPushNotificationReceiptsAsync(chunk);
|
|
||||||
console.log('Receipts:', receipts);
|
|
||||||
|
|
||||||
for (let receiptId in receipts) {
|
|
||||||
let { status, message, details } = receipts[receiptId];
|
|
||||||
if (status === 'ok') {
|
|
||||||
console.log(`Notification with receipt ID ${receiptId} was delivered successfully`);
|
|
||||||
} else if (status === 'error') {
|
|
||||||
console.error(`Notification error: ${message}`);
|
|
||||||
if (details && details.error) {
|
|
||||||
console.error(`Error details: ${details.error}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error retrieving receipts:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
exports.createSubUser = onRequest(async (request, response) => {
|
exports.createSubUser = onRequest(async (request, response) => {
|
||||||
const authHeader = request.get('Authorization');
|
const authHeader = request.get('Authorization');
|
||||||
|
|
||||||
@ -203,7 +191,7 @@ exports.refreshTokens = functions.pubsub.schedule('every 12 hours').onRun(async
|
|||||||
if (profileData.googleToken) {
|
if (profileData.googleToken) {
|
||||||
try {
|
try {
|
||||||
const refreshedGoogleToken = await refreshGoogleToken(profileData.googleToken);
|
const refreshedGoogleToken = await refreshGoogleToken(profileData.googleToken);
|
||||||
await profileDoc.ref.update({ googleToken: refreshedGoogleToken });
|
await profileDoc.ref.update({googleToken: refreshedGoogleToken});
|
||||||
console.log(`Google token updated for user ${profileDoc.id}`);
|
console.log(`Google token updated for user ${profileDoc.id}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error refreshing Google token for user ${profileDoc.id}:`, error.message);
|
console.error(`Error refreshing Google token for user ${profileDoc.id}:`, error.message);
|
||||||
@ -213,7 +201,7 @@ exports.refreshTokens = functions.pubsub.schedule('every 12 hours').onRun(async
|
|||||||
if (profileData.microsoftToken) {
|
if (profileData.microsoftToken) {
|
||||||
try {
|
try {
|
||||||
const refreshedMicrosoftToken = await refreshMicrosoftToken(profileData.microsoftToken);
|
const refreshedMicrosoftToken = await refreshMicrosoftToken(profileData.microsoftToken);
|
||||||
await profileDoc.ref.update({ microsoftToken: refreshedMicrosoftToken });
|
await profileDoc.ref.update({microsoftToken: refreshedMicrosoftToken});
|
||||||
console.log(`Microsoft token updated for user ${profileDoc.id}`);
|
console.log(`Microsoft token updated for user ${profileDoc.id}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error refreshing Microsoft token for user ${profileDoc.id}:`, error.message);
|
console.error(`Error refreshing Microsoft token for user ${profileDoc.id}:`, error.message);
|
||||||
@ -223,7 +211,7 @@ exports.refreshTokens = functions.pubsub.schedule('every 12 hours').onRun(async
|
|||||||
if (profileData.appleToken) {
|
if (profileData.appleToken) {
|
||||||
try {
|
try {
|
||||||
const refreshedAppleToken = await refreshAppleToken(profileData.appleToken);
|
const refreshedAppleToken = await refreshAppleToken(profileData.appleToken);
|
||||||
await profileDoc.ref.update({ appleToken: refreshedAppleToken });
|
await profileDoc.ref.update({appleToken: refreshedAppleToken});
|
||||||
console.log(`Apple token updated for user ${profileDoc.id}`);
|
console.log(`Apple token updated for user ${profileDoc.id}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error refreshing Apple token for user ${profileDoc.id}:`, error.message);
|
console.error(`Error refreshing Apple token for user ${profileDoc.id}:`, error.message);
|
||||||
@ -289,4 +277,8 @@ async function getPushTokensForFamilyExcludingCreator(familyId, creatorId) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return pushTokens;
|
return pushTokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeInvalidPushToken(pushToken) {
|
||||||
|
// TODO
|
||||||
}
|
}
|
@ -9,7 +9,8 @@ export const useFetchAndSaveAppleEvents = () => {
|
|||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationKey: ["fetchAndSaveAppleEvents"],
|
mutationKey: ["fetchAndSaveAppleEvents"],
|
||||||
mutationFn: async (token?: string, email?: string) => {
|
mutationFn: async ({token, email}: { token?: string, email?: string }) => {
|
||||||
|
console.log("CALLL")
|
||||||
const timeMin = new Date(new Date().setFullYear(new Date().getFullYear() - 1));
|
const timeMin = new Date(new Date().setFullYear(new Date().getFullYear() - 1));
|
||||||
const timeMax = new Date(new Date().setFullYear(new Date().getFullYear() + 5));
|
const timeMax = new Date(new Date().setFullYear(new Date().getFullYear() + 5));
|
||||||
try {
|
try {
|
||||||
|
@ -9,7 +9,7 @@ export const useFetchAndSaveGoogleEvents = () => {
|
|||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationKey: ["fetchAndSaveGoogleEvents"],
|
mutationKey: ["fetchAndSaveGoogleEvents"],
|
||||||
mutationFn: async (token?: string, email?: string) => {
|
mutationFn: async ({token, email}: { token?: string; email?: string }) => {
|
||||||
console.log("Fetching Google Calendar events...");
|
console.log("Fetching Google Calendar events...");
|
||||||
const timeMin = new Date(new Date().setFullYear(new Date().getFullYear() - 1));
|
const timeMin = new Date(new Date().setFullYear(new Date().getFullYear() - 1));
|
||||||
const timeMax = new Date(new Date().setFullYear(new Date().getFullYear() + 5));
|
const timeMax = new Date(new Date().setFullYear(new Date().getFullYear() + 5));
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { useMutation } from "react-query";
|
import {useMutation} from "react-query";
|
||||||
import { useAuthContext } from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
import { useCreateEventsFromProvider } from "@/hooks/firebase/useCreateEvent";
|
import {useCreateEventsFromProvider} from "@/hooks/firebase/useCreateEvent";
|
||||||
import { fetchMicrosoftCalendarEvents } from "@/calendar-integration/microsoft-calendar-utils";
|
import {fetchMicrosoftCalendarEvents} from "@/calendar-integration/microsoft-calendar-utils";
|
||||||
|
|
||||||
export const useFetchAndSaveOutlookEvents = () => {
|
export const useFetchAndSaveOutlookEvents = () => {
|
||||||
const { profileData } = useAuthContext();
|
const {profileData} = useAuthContext();
|
||||||
const { mutateAsync: createEventsFromProvider } = useCreateEventsFromProvider();
|
const {mutateAsync: createEventsFromProvider} = useCreateEventsFromProvider();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationKey: ["fetchAndSaveOutlookEvents"],
|
mutationKey: ["fetchAndSaveOutlookEvents"],
|
||||||
mutationFn: async (token?: string, email?: string) => {
|
mutationFn: async ({token, email}: { token?: string; email?: string }) => {
|
||||||
const timeMin = new Date(new Date().setFullYear(new Date().getFullYear() - 1));
|
const timeMin = new Date(new Date().setFullYear(new Date().getFullYear() - 1));
|
||||||
const timeMax = new Date(new Date().setFullYear(new Date().getFullYear() + 3));
|
const timeMax = new Date(new Date().setFullYear(new Date().getFullYear() + 3));
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user