Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: sound on browser push notification #18548

Merged
merged 8 commits into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix: notification
  • Loading branch information
Udit-takkar committed Mar 10, 2025
commit 7f1984719c197983f45b79278caf23efc2426d2c
119 changes: 68 additions & 51 deletions apps/web/public/service-worker.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
self.addEventListener("push", async (event) => {
if (!event.data) return

let notificationData = event.data.json();

const allClients = await clients.matchAll({
Expand All @@ -14,54 +16,64 @@ self.addEventListener("push", async (event) => {
const title = notificationData.title || "New Cal.com Notification";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The service worker would trigger notification and send message to play sound if notification type is instant meetings

const image = notificationData.icon || "https://cal.com/api/logo?type=icon";

// Special handling for instant meetings
if (notificationData.data?.type === "INSTANT_MEETING") {
allClients.forEach(client => {
client.postMessage({
type: 'PLAY_NOTIFICATION_SOUND'
});
});
}
event.waitUntil(
(async () => {
try {
// Close notifications with the same tag if it exists
const existingNotifications = await self.registration.getNotifications({
tag: notificationData.tag
});

const existingNotifications = await self.registration.getNotifications();

existingNotifications.forEach((notification) => {
const options = {
body: notification.body,
icon: notification.icon,
badge: notification.badge,
data: notification.data,
silent: notification.silent,
vibrate: notification.vibrate,
requireInteraction: notification.requireInteraction,
tag: notification.tag,
};
existingNotifications.forEach((notification) => {
const options = {
body: notification.body,
icon: notification.icon,
badge: notification.badge,
data: notification.data,
silent: notification.silent,
vibrate: notification.vibrate,
requireInteraction: notification.requireInteraction,
tag: notification.tag,
};

self.registration.showNotification(notification.title, options);
});
self.registration.showNotification(notification.title, options);
});

const notificationOptions = {
body: notificationData.body,
icon: image,
badge: image,
data: notificationData.data,
tag: notificationData.tag || `cal-notification-${Date.now()}`,
renotify: true,
requireInteraction: notificationData.requireInteraction ?? true,
actions: notificationData.actions || [],
vibrate: [200, 100, 200],
};

try {
await self.registration.showNotification(title, notificationOptions);
console.log("Notification shown successfully");
} catch (error) {
console.error("Error showing notification:", error);
}
// Special handling for instant meetings
if (notificationData.data?.type === "INSTANT_MEETING") {
allClients.forEach(client => {
client.postMessage({
type: 'PLAY_NOTIFICATION_SOUND'
});
});
}

const notificationOptions = {
body: notificationData.body,
icon: image,
badge: image,
data: notificationData.data,
tag: notificationData.tag || `cal-notification-${Date.now()}`,
renotify: true,
requireInteraction: notificationData.requireInteraction ?? true,
actions: notificationData.actions || [],
vibrate: [200, 100, 200],
urgency: 'high'
};

console.log("notificationOptions", notificationOptions);

await self.registration.showNotification(title, notificationOptions);
console.log("Notification shown successfully");
} catch (error) {
console.error("Error showing notification:", error);
}
})()
);
});

self.addEventListener("notificationclick", (event) => {

self.addEventListener("notificationclick", (event) => {
if (event.notification.data?.type === "INSTANT_MEETING") {
const stopSound = async () => {
const allClients = await clients.matchAll({
Expand All @@ -75,16 +87,21 @@ self.addEventListener("notificationclick", (event) => {
});
});
};
}

if (!event.action) {
// Normal Notification Click
event.notification.close();
const url = event.notification.data.url;
event.waitUntil(self.clients.openWindow(url));
}

event.waitUntil(Promise.all([
stopSound(),
clients.openWindow(event.notification.data.url)
]));
} else {
// Handle regular notifications
event.waitUntil(
clients.openWindow(event.notification.data?.url || "https://app.cal.com")
);
switch (event.action) {
case "connect-action":
event.notification.close();
const url = event.notification.data.url;
event.waitUntil(self.clients.openWindow(url));
break;
}
});

Expand Down
10 changes: 1 addition & 9 deletions packages/features/instant-meeting/handleInstantMeeting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,7 @@ const triggerBrowserNotifications = async (args: {
body: "User is waiting for you to join. Click to Connect",
url: connectAndJoinUrl,
type: "INSTANT_MEETING",
requireInteraction: true,
actions: [
{
action: "connect-action",
title: "Connect and join",
type: "button",
image: "https://cal.com/api/logo?type=icon",
},
],
requireInteraction: false,
});
});

Expand Down
2 changes: 1 addition & 1 deletion packages/features/notifications/sendNotification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const vapidKeys = {
};

// The mail to email address should be the one at which push service providers can reach you. It can also be a URL.
webpush.setVapidDetails("https://cal.com", vapidKeys.publicKey, vapidKeys.privateKey);
webpush.setVapidDetails("mailto:support@cal.com", vapidKeys.publicKey, vapidKeys.privateKey);

type Subscription = {
endpoint: string;
Expand Down