* feat: web push notifications feature * Revert yarn.lock changes * added new env variables requirement in .env.example * moved useNotifications hook in packages/lib/hooks * fix: bug * use i18n * chore: move to new file * chore: add yarn.lock * Update .env.example Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com> * chore: add instruction for brave browser * fix: tooltip * chore: use enum * chore * small update --------- Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Co-authored-by: Udit Takkar <udit222001@gmail.com> Co-authored-by: Peer Richelsen <peer@cal.com> Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com> Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com> Co-authored-by: unknown <adhabal2002@gmail.com> Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
165 lines
4.7 KiB
TypeScript
165 lines
4.7 KiB
TypeScript
import { useState, useEffect } from "react";
|
|
|
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|
import { trpc } from "@calcom/trpc/react";
|
|
import { showToast } from "@calcom/ui";
|
|
|
|
export enum ButtonState {
|
|
NONE = "none",
|
|
ALLOW = "allow",
|
|
DISABLE = "disable",
|
|
DENIED = "denied",
|
|
}
|
|
|
|
export const useNotifications = () => {
|
|
const [buttonToShow, setButtonToShow] = useState<ButtonState>(ButtonState.NONE);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const { t } = useLocale();
|
|
|
|
const { mutate: addSubscription } = trpc.viewer.addNotificationsSubscription.useMutation({
|
|
onSuccess: () => {
|
|
setButtonToShow(ButtonState.DISABLE);
|
|
showToast(t("browser_notifications_turned_on"), "success");
|
|
},
|
|
onError: (error) => {
|
|
showToast(`Error: ${error.message}`, "error");
|
|
},
|
|
onSettled: () => {
|
|
setIsLoading(false);
|
|
},
|
|
});
|
|
const { mutate: removeSubscription } = trpc.viewer.removeNotificationsSubscription.useMutation({
|
|
onSuccess: () => {
|
|
setButtonToShow(ButtonState.ALLOW);
|
|
showToast(t("browser_notifications_turned_off"), "success");
|
|
},
|
|
onError: (error) => {
|
|
showToast(`Error: ${error.message}`, "error");
|
|
},
|
|
onSettled: () => {
|
|
setIsLoading(false);
|
|
},
|
|
});
|
|
|
|
useEffect(() => {
|
|
const decideButtonToShow = async () => {
|
|
if (!("Notification" in window)) {
|
|
console.log("Notifications not supported");
|
|
}
|
|
|
|
const registration = await navigator.serviceWorker?.getRegistration();
|
|
if (!registration) return;
|
|
const subscription = await registration.pushManager.getSubscription();
|
|
|
|
const permission = Notification.permission;
|
|
|
|
if (permission === ButtonState.DENIED) {
|
|
setButtonToShow(ButtonState.DENIED);
|
|
return;
|
|
}
|
|
|
|
if (permission === "default") {
|
|
setButtonToShow(ButtonState.ALLOW);
|
|
return;
|
|
}
|
|
|
|
if (!subscription) {
|
|
setButtonToShow(ButtonState.ALLOW);
|
|
return;
|
|
}
|
|
|
|
setButtonToShow(ButtonState.DISABLE);
|
|
};
|
|
|
|
decideButtonToShow();
|
|
}, []);
|
|
|
|
const enableNotifications = async () => {
|
|
setIsLoading(true);
|
|
const permissionResponse = await Notification.requestPermission();
|
|
|
|
if (permissionResponse === ButtonState.DENIED) {
|
|
setButtonToShow(ButtonState.DENIED);
|
|
setIsLoading(false);
|
|
showToast(t("browser_notifications_denied"), "warning");
|
|
return;
|
|
}
|
|
|
|
if (permissionResponse === "default") {
|
|
setIsLoading(false);
|
|
showToast(t("please_allow_notifications"), "warning");
|
|
return;
|
|
}
|
|
|
|
const registration = await navigator.serviceWorker?.getRegistration();
|
|
if (!registration) {
|
|
// This will not happen ideally as the button will not be shown if the service worker is not registered
|
|
return;
|
|
}
|
|
|
|
let subscription: PushSubscription;
|
|
try {
|
|
subscription = await registration.pushManager.subscribe({
|
|
userVisibleOnly: true,
|
|
applicationServerKey: urlB64ToUint8Array(process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY || ""),
|
|
});
|
|
} catch (error) {
|
|
// This happens in Brave browser as it does not have a push service
|
|
console.error(error);
|
|
setIsLoading(false);
|
|
setButtonToShow(ButtonState.NONE);
|
|
showToast(t("browser_notifications_not_supported"), "error");
|
|
return;
|
|
}
|
|
|
|
addSubscription(
|
|
{ subscription: JSON.stringify(subscription) },
|
|
{
|
|
onError: async () => {
|
|
await subscription.unsubscribe();
|
|
},
|
|
}
|
|
);
|
|
};
|
|
|
|
const disableNotifications = async () => {
|
|
setIsLoading(true);
|
|
const registration = await navigator.serviceWorker?.getRegistration();
|
|
if (!registration) {
|
|
// This will not happen ideally as the button will not be shown if the service worker is not registered
|
|
return;
|
|
}
|
|
const subscription = await registration.pushManager.getSubscription();
|
|
if (!subscription) {
|
|
// This will not happen ideally as the button will not be shown if the subscription is not present
|
|
return;
|
|
}
|
|
removeSubscription(
|
|
{ subscription: JSON.stringify(subscription) },
|
|
{
|
|
onSuccess: async () => {
|
|
await subscription.unsubscribe();
|
|
},
|
|
}
|
|
);
|
|
};
|
|
|
|
return {
|
|
buttonToShow,
|
|
isLoading,
|
|
enableNotifications,
|
|
disableNotifications,
|
|
};
|
|
};
|
|
|
|
const urlB64ToUint8Array = (base64String: string) => {
|
|
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
|
|
const base64 = (base64String + padding).replace(/\-/g, "+").replace(/_/g, "/");
|
|
const rawData = window.atob(base64);
|
|
const outputArray = new Uint8Array(rawData.length);
|
|
for (let i = 0; i < rawData.length; ++i) {
|
|
outputArray[i] = rawData.charCodeAt(i);
|
|
}
|
|
return outputArray;
|
|
};
|