send notifications over SSE to be displayed inside the app
This commit is contained in:
@ -5,7 +5,8 @@ import useAppLoaderData from "~/features/core/hooks/use-app-loader-data";
|
||||
|
||||
export default function useNotifications() {
|
||||
const isServiceWorkerSupported = useMemo(() => "serviceWorker" in navigator, []);
|
||||
const [subscription, setSubscription] = useState<PushSubscription | null>(null);
|
||||
const isWebPushSupported = useMemo(() => "PushManager" in window, []);
|
||||
const [subscription, setSubscription] = useState<PushSubscription | null>();
|
||||
const { webPushPublicKey } = useAppLoaderData().config;
|
||||
const fetcher = useFetcher();
|
||||
const subscribeToNotifications = (subscription: PushSubscriptionJSON) => {
|
||||
@ -29,7 +30,7 @@ export default function useNotifications() {
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (!isServiceWorkerSupported) {
|
||||
if (!isServiceWorkerSupported || !isWebPushSupported) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -37,10 +38,10 @@ export default function useNotifications() {
|
||||
const subscription = await registration.pushManager.getSubscription();
|
||||
setSubscription(subscription);
|
||||
})();
|
||||
}, [isServiceWorkerSupported]);
|
||||
}, [isServiceWorkerSupported, isWebPushSupported]);
|
||||
|
||||
async function subscribe() {
|
||||
if (!isServiceWorkerSupported || subscription !== null || fetcher.state !== "idle") {
|
||||
if (!isServiceWorkerSupported || !isWebPushSupported || subscription !== null || fetcher.state !== "idle") {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -54,7 +55,7 @@ export default function useNotifications() {
|
||||
}
|
||||
|
||||
async function unsubscribe() {
|
||||
if (!isServiceWorkerSupported || !subscription || fetcher.state !== "idle") {
|
||||
if (!isServiceWorkerSupported || !isWebPushSupported || !subscription || fetcher.state !== "idle") {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -69,7 +70,7 @@ export default function useNotifications() {
|
||||
}
|
||||
|
||||
return {
|
||||
isServiceWorkerSupported,
|
||||
isNotificationSupported: isServiceWorkerSupported && isWebPushSupported,
|
||||
subscription,
|
||||
subscribe,
|
||||
unsubscribe,
|
||||
|
@ -7,22 +7,39 @@ export default function useNotifications() {
|
||||
const [notificationData, setNotificationData] = useAtom(notificationDataAtom);
|
||||
|
||||
useEffect(() => {
|
||||
const channel = new BroadcastChannel("notifications");
|
||||
const eventSource = new EventSource("/sse/notifications");
|
||||
eventSource.addEventListener("message", onMessage);
|
||||
|
||||
return () => {
|
||||
eventSource.removeEventListener("message", onMessage);
|
||||
eventSource.close();
|
||||
};
|
||||
|
||||
function onMessage(event: MessageEvent) {
|
||||
console.log("event.data", JSON.parse(event.data));
|
||||
const notifyChannel = new BroadcastChannel("notifications");
|
||||
notifyChannel.postMessage(event.data);
|
||||
notifyChannel.close();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const notifyChannel = new BroadcastChannel("notifications");
|
||||
async function eventHandler(event: MessageEvent) {
|
||||
const payload: NotificationPayload = JSON.parse(event.data);
|
||||
setNotificationData(payload);
|
||||
}
|
||||
|
||||
channel.addEventListener("message", eventHandler);
|
||||
notifyChannel.addEventListener("message", eventHandler);
|
||||
|
||||
return () => {
|
||||
channel.removeEventListener("message", eventHandler);
|
||||
channel.close();
|
||||
notifyChannel.removeEventListener("message", eventHandler);
|
||||
notifyChannel.close();
|
||||
};
|
||||
}, [setNotificationData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!notificationData) {
|
||||
if (!notificationData || notificationData.data.type === "call") {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { useCallback, useEffect } from "react";
|
||||
import type { Call } from "@twilio/voice-sdk";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { notificationDataAtom } from "~/features/core/hooks/use-notifications";
|
||||
|
||||
export default function useCall() {
|
||||
const [, setNotificationData] = useAtom(notificationDataAtom);
|
||||
const [call, setCall] = useAtom(callAtom);
|
||||
const endCall = useCallback(
|
||||
function endCallFn() {
|
||||
@ -10,8 +12,9 @@ export default function useCall() {
|
||||
call?.removeListener("disconnect", endCall);
|
||||
call?.disconnect();
|
||||
setCall(null);
|
||||
setNotificationData(null);
|
||||
},
|
||||
[call, setCall],
|
||||
[call, setCall, setNotificationData],
|
||||
);
|
||||
const onError = useCallback(
|
||||
function onErrorFn(error: any) {
|
||||
@ -19,9 +22,10 @@ export default function useCall() {
|
||||
call?.removeListener("disconnect", endCall);
|
||||
call?.disconnect();
|
||||
setCall(null);
|
||||
setNotificationData(null);
|
||||
throw error; // TODO: might not get caught by error boundary
|
||||
},
|
||||
[call, setCall, endCall],
|
||||
[call, setCall, endCall, setNotificationData],
|
||||
);
|
||||
|
||||
const eventHandlers = [
|
||||
|
@ -82,8 +82,7 @@ export default function useDevice() {
|
||||
|
||||
setCall(incomingCall);
|
||||
console.log("incomingCall.parameters", incomingCall.parameters);
|
||||
// TODO prevent making a new call when there is a pending incoming call
|
||||
const channel = new BroadcastChannel("notifications");
|
||||
const notifyChannel = new BroadcastChannel("notifications");
|
||||
const recipient = incomingCall.parameters.From;
|
||||
const message: NotificationPayload = {
|
||||
title: recipient, // TODO:
|
||||
@ -100,7 +99,8 @@ export default function useDevice() {
|
||||
],
|
||||
data: { recipient, type: "call" },
|
||||
};
|
||||
channel.postMessage(JSON.stringify(message));
|
||||
notifyChannel.postMessage(JSON.stringify(message));
|
||||
notifyChannel.close();
|
||||
},
|
||||
[call, setCall],
|
||||
);
|
||||
|
@ -3,9 +3,13 @@ import useNotifications from "~/features/core/hooks/use-notifications.client";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function NotificationsSettings() {
|
||||
const { subscription, subscribe, unsubscribe } = useNotifications();
|
||||
const { isNotificationSupported, subscription, subscribe, unsubscribe } = useNotifications();
|
||||
const [notificationsEnabled, setNotificationsEnabled] = useState(!!subscription);
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const [errorMessage, setErrorMessage] = useState(() =>
|
||||
isNotificationSupported
|
||||
? ""
|
||||
: "Your browser does not support web push notifications. You will still receive in-app notifications as long as you have Shellphone open.",
|
||||
);
|
||||
const [isChanging, setIsChanging] = useState(false);
|
||||
const onChange = async (checked: boolean) => {
|
||||
if (isChanging) {
|
||||
@ -46,6 +50,11 @@ export default function NotificationsSettings() {
|
||||
setNotificationsEnabled(!!subscription);
|
||||
}, [subscription]);
|
||||
|
||||
if (typeof subscription === "undefined") {
|
||||
return <FallbackNotificationsSettings />;
|
||||
}
|
||||
|
||||
// TODO: allow disabling in-app notifications
|
||||
return (
|
||||
<ul className="mt-2 divide-y divide-gray-200">
|
||||
<Toggle
|
||||
|
Reference in New Issue
Block a user