sync update

This commit is contained in:
Milan Paunovic
2024-11-02 12:00:49 +01:00
parent a6b2c4b97f
commit 8a0370933d
4 changed files with 209 additions and 7 deletions

View File

@ -14,6 +14,11 @@ let notificationTimeout = null;
let eventCount = 0;
let pushTokens = [];
const GOOGLE_CALENDAR_ID = "primary";
const CHANNEL_ID = "unique-channel-id";
const WEBHOOK_URL = "https://us-central1-cally-family-calendar.cloudfunctions.net/sendSyncNotification";
exports.sendNotificationOnEventCreation = functions.firestore
.document('Events/{eventId}')
.onCreate(async (snapshot, context) => {
@ -304,4 +309,150 @@ async function getPushTokensForFamilyExcludingCreator(familyId, creatorId) {
async function removeInvalidPushToken(pushToken) {
// TODO
}
}
async function refreshGoogleToken(refreshToken) {
try {
const response = await axios.post("https://oauth2.googleapis.com/token", {
grant_type: "refresh_token",
refresh_token: refreshToken,
client_id: "406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com",
});
return response.data.access_token;
} catch (error) {
console.error("Error refreshing Google token:", error.message);
throw error;
}
}
// Helper function to get Google access tokens for all users and refresh them if needed
async function getGoogleAccessTokens() {
const tokens = {};
const profilesSnapshot = await db.collection("Profiles").get();
await Promise.all(
profilesSnapshot.docs.map(async (doc) => {
const profileData = doc.data();
const googleAccounts = profileData.googleAccounts || {};
for (const googleEmail of Object.keys(googleAccounts)) {
const { refreshToken } = googleAccounts[googleEmail];
if (refreshToken) {
try {
const accessToken = await refreshGoogleToken(refreshToken);
tokens[doc.id] = accessToken;
} catch (error) {
console.error(`Failed to refresh token for user ${doc.id}:`, error.message);
}
}
}
})
);
return tokens;
}
// Function to watch Google Calendar events
const watchCalendarEvents = async (userId, token) => {
const url = `https://www.googleapis.com/calendar/v3/calendars/${GOOGLE_CALENDAR_ID}/events/watch`;
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
id: `${CHANNEL_ID}-${userId}`, // Unique ID per user
type: "web_hook",
address: `${WEBHOOK_URL}?userId=${userId}`, // Pass userId to identify notifications
params: {
ttl: "80000", // Set to 20 hours in seconds
},
}),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(`Failed to watch calendar: ${errorData.error?.message || response.statusText}`);
}
return response.json();
};
// Schedule function to renew Google Calendar watch every 20 hours for each user
exports.renewGoogleCalendarWatch = functions.pubsub.schedule("every 20 hours").onRun(async (context) => {
try {
const tokens = await getGoogleAccessTokens();
for (const [userId, token] of Object.entries(tokens)) {
try {
const result = await watchCalendarEvents(userId, token);
console.log(`Successfully renewed Google Calendar watch for user ${userId}:`, result);
} catch (error) {
console.error(`Error renewing Google Calendar watch for user ${userId}:`, error.message);
}
}
} catch (error) {
console.error("Error in renewGoogleCalendarWatch function:", error.message);
}
});
// Function to handle notifications from Google Calendar for a specific user
exports.sendSyncNotification = functions.https.onRequest(async (req, res) => {
const userId = req.query.userId; // Extract userId from query params
const calendarId = req.body.resourceId;
// Fetch push tokens for the specific user
const userDoc = await db.collection("Profiles").doc(userId).get();
const userData = userDoc.data();
const pushTokens = userData ? userData.pushToken : [];
if (pushTokens.length === 0) {
console.log(`No push tokens found for user ${userId}`);
res.status(200).send("No push tokens found for user.");
return;
}
const syncMessage = "New events have been synced.";
let messages = pushTokens.map(pushToken => {
if (!Expo.isExpoPushToken(pushToken)) {
console.error(`Push token ${pushToken} is not a valid Expo push token`);
return null;
}
return {
to: pushToken,
sound: "default",
title: "Event Sync",
body: syncMessage,
data: { userId, calendarId },
};
}).filter(Boolean);
let chunks = expo.chunkPushNotifications(messages);
let tickets = [];
for (let chunk of chunks) {
try {
let ticketChunk = await expo.sendPushNotificationsAsync(chunk);
tickets.push(...ticketChunk);
for (let ticket of ticketChunk) {
if (ticket.status === "ok") {
console.log("Notification successfully sent:", ticket.id);
} else if (ticket.status === "error") {
console.error(`Notification error: ${ticket.message}`);
if (ticket.details?.error === "DeviceNotRegistered") {
await removeInvalidPushToken(ticket.to);
}
}
}
} catch (error) {
console.error("Error sending notification:", error.message);
}
}
res.status(200).send("Sync notification sent.");
});