diff --git a/app/entry.worker.ts b/app/entry.worker.ts index 1f462b7..8af55b8 100644 --- a/app/entry.worker.ts +++ b/app/entry.worker.ts @@ -1,47 +1,29 @@ /// -import type { NotificationPayload } from "~/utils/web-push.server"; -import { addBadge, removeBadge } from "~/utils/pwa.client"; +import handleInstall from "./service-worker/install"; +import handleActivate from "./service-worker/activate"; +import handlePush from "./service-worker/push"; +import handleNotificationClick from "./service-worker/notification-click"; +import handleFetch from "./service-worker/fetch"; declare let self: ServiceWorkerGlobalScope; -const defaultOptions: NotificationOptions = { - icon: "/icons/android-chrome-192x192.png", - badge: "/icons/android-chrome-48x48.png", - dir: "auto", - image: undefined, - silent: false, -}; +self.addEventListener("install", (event) => { + event.waitUntil(handleInstall(event).then(() => self.skipWaiting())); +}); + +self.addEventListener("activate", (event) => { + event.waitUntil(handleActivate(event).then(() => self.clients.claim())); +}); self.addEventListener("push", (event) => { - const { title, ...payload }: NotificationPayload = JSON.parse(event?.data!.text()); - const options = Object.assign({}, defaultOptions, payload); - event.waitUntil(async () => { - await self.registration.showNotification(title, options); - await addBadge(1); - }); + event.waitUntil(handlePush(event)); }); self.addEventListener("notificationclick", (event) => { - event.waitUntil( - (async () => { - console.log("On notification click: ", event.notification.tag); - // Android doesn’t close the notification when you click on it - // See: http://crbug.com/463146 - event.notification.close(); - await removeBadge(); - - if (event.action === "reply") { - const recipient = encodeURIComponent(event.notification.data.recipient); - return self.clients.openWindow?.(`/messages/${recipient}`); - } - - if (event.action === "answer") { - const recipient = encodeURIComponent(event.notification.data.recipient); - return self.clients.openWindow?.(`/incoming-call/${recipient}`); - } - - return self.clients.openWindow?.("/"); - })(), - ); + event.waitUntil(handleNotificationClick(event)); +}); + +self.addEventListener("fetch", (event) => { + event.respondWith(handleFetch(event)); }); diff --git a/app/root.tsx b/app/root.tsx index baf0265..c10cc97 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -1,4 +1,4 @@ -import { type FunctionComponent, type PropsWithChildren } from "react"; +import type { FunctionComponent, PropsWithChildren } from "react"; import type { LinksFunction } from "@remix-run/node"; import { Link, Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration, useCatch } from "@remix-run/react"; diff --git a/app/routes/__app/settings/notifications.tsx b/app/routes/__app/settings/notifications.tsx index cfbb6f2..ddd60e5 100644 --- a/app/routes/__app/settings/notifications.tsx +++ b/app/routes/__app/settings/notifications.tsx @@ -2,12 +2,12 @@ import type { ActionFunction } from "@remix-run/node"; import { ClientOnly } from "remix-utils"; import { Form } from "@remix-run/react"; +import db from "~/utils/db.server"; import { notify } from "~/utils/web-push.server"; import Button from "~/features/settings/components/button"; import NotificationsSettings, { FallbackNotificationsSettings, } from "~/features/settings/components/settings/notifications-settings"; -import db from "~/utils/db.server"; export const action: ActionFunction = async () => { const phoneNumber = await db.phoneNumber.findUnique({ @@ -34,7 +34,7 @@ export const action: ActionFunction = async () => { title: "Reply", }, ], - data: { recipient: "+33613370787" }, + data: { recipient: "+33613370787", type: "message" }, }); return null; }; @@ -48,7 +48,7 @@ export default function Notifications() {
-
+ diff --git a/app/service-worker/activate.ts b/app/service-worker/activate.ts new file mode 100644 index 0000000..b45d317 --- /dev/null +++ b/app/service-worker/activate.ts @@ -0,0 +1,10 @@ +declare let self: ServiceWorkerGlobalScope; + +export default async function handleActivate(event: ExtendableEvent) { + console.debug("Service worker activated"); + // @ts-ignore + if (self.registration.navigationPreload) { + // @ts-ignore + await self.registration.navigationPreload.enable(); + } +} diff --git a/app/service-worker/fetch.ts b/app/service-worker/fetch.ts new file mode 100644 index 0000000..ad2355b --- /dev/null +++ b/app/service-worker/fetch.ts @@ -0,0 +1,10 @@ +declare let self: ServiceWorkerGlobalScope; + +export default async function handleFetch(event: FetchEvent & { preloadResponse?: Promise }) { + const preloaded = await event.preloadResponse; + if (preloaded) { + return preloaded; + } + + return fetch(event.request); +} diff --git a/app/service-worker/install.ts b/app/service-worker/install.ts new file mode 100644 index 0000000..3ed9c6f --- /dev/null +++ b/app/service-worker/install.ts @@ -0,0 +1,5 @@ +declare let self: ServiceWorkerGlobalScope; + +export default async function handleInstall(event: ExtendableEvent) { + console.debug("Service worker installed"); +} diff --git a/app/service-worker/notification-click.ts b/app/service-worker/notification-click.ts new file mode 100644 index 0000000..a448412 --- /dev/null +++ b/app/service-worker/notification-click.ts @@ -0,0 +1,32 @@ +import { removeBadge } from "~/utils/pwa.client"; + +declare let self: ServiceWorkerGlobalScope; + +// noinspection TypeScriptUnresolvedVariable +export default async function handleNotificationClick(event: NotificationEvent) { + console.debug("On notification click: ", event.notification.tag); + // Android doesn’t close the notification when you click on it + // See: http://crbug.com/463146 + event.notification.close(); + await removeBadge(); + + const url = getUrl(event.notification.data); + return self.clients.openWindow?.(url); +} + +type NotificationData = { + recipient: string; + type: "message" | "incoming-call"; +}; + +function getUrl(data: NotificationData) { + const recipient = encodeURIComponent(data.recipient); + switch (data.type) { + case "message": + return `/messages/${recipient}`; + case "incoming-call": + return `/incoming-call/${recipient}`; + default: + return "/messages"; + } +} diff --git a/app/service-worker/push.ts b/app/service-worker/push.ts new file mode 100644 index 0000000..8f8f25b --- /dev/null +++ b/app/service-worker/push.ts @@ -0,0 +1,19 @@ +import type { NotificationPayload } from "~/utils/web-push.server"; +import { addBadge } from "~/utils/pwa.client"; + +declare let self: ServiceWorkerGlobalScope; + +const defaultOptions: NotificationOptions = { + icon: "/icons/android-chrome-192x192.png", + badge: "/icons/android-chrome-48x48.png", + dir: "auto", + image: undefined, + silent: false, +}; + +export default async function handlePush(event: PushEvent) { + const { title, ...payload }: NotificationPayload = event.data!.json(); + const options = Object.assign({}, defaultOptions, payload); + await self.registration.showNotification(title, options); + await addBadge(1); +}