cheogram-www
created pr with
cheogram-www/tauri-alpha.1
commented
Changes are available at https://tangled.org/mrry.band/cheogram-www on branch tauri-alpha.
added cheogram-www/tauri-alpha.2
1: 45e8ad7 = 1: 45e8ad7 Refactor service worker access
2: fab4022 ! 2: ce66e8f Move notifications code to one file
-: ------- > 3: 10e3574 Refactor notification code
3: d127ee0 ! 4: ba0891f npm run tauri init
4: 9e006bd = 5: 5b3abf9 Request microphone and camera usage on mac
5: 03d4934 = 6: 994a2e9 Tauri file system media store
6: b96216a = 7: 320fa8f Don't register or use serviceworker in tauri
7: faace0a = 8: e76db17 Open links in browser
8: 9c07308 ! 9: 761dd22 Tauri notifications
cmds
checkout latest patchset:
ssh pr.pico.sh pull cheogram-www:tauri-alpha | git am -3checkout specific patchset revision:
ssh pr.pico.sh pull cheogram-www:tauri-alpha [rev] | git am -3add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh cheogram-www:tauri-alphaadd comment to patch request:
ssh pr.pico.sh comment cheogram-www:tauri-alpha "lgtm!"
Patchset
cheogram-www/tauri-alpha.1
→ Refactor service worker access
Eric Roberts
Refactor notification related code
2026-10-29T14:50:23ZEric Roberts
npm run tauri init
2026-10-29T15:06:32ZEric Roberts
Request microphone and camera usage on mac
2026-10-16T14:01:30ZEric Roberts
Tauri file system media store
2026-10-20T19:29:32ZEric Roberts
Don't register or use serviceworker in tauri
2026-10-16T18:10:16ZEric Roberts
Open links in browser
2026-10-19T18:23:24ZEric Roberts
Tauri notifications
2026-10-20T18:39:35ZEric Roberts
2026-10-19T19:40:11Z
Refactor service worker access
Eric Roberts
2026-10-29T14:50:23ZIn the Tauri app we can't use the service worker because it needs to be registered on https:// or localhost, and the tauri page runs at tauri://localhost. But in tauri `navigator.serviceWorker` is truthy still, and if you await ready it will just hang forever. So this refactors the way we access the service worker before actually making the change to Tauri. Omitting from Tauri will then become a one line change. We've moved away from the automatic registration because the future Tauri change will make `serviceWorker` from src/util.ts falsey, which will then control the registration.
Semantic diff summary
0 added,
22 modified,
0 signature changed,
0 removed
across 9 analyzed files
+11
-5
src/App.tsx
#
| ... | ... | @@ -13,7 +13,14 @@ import { | |
| 13 | 13 | useNavigate, | |
| 14 | 14 | useParams, | |
| 15 | 15 | } from "react-router"; | |
| 16 | - | import { ios, webkit, href, useEffectEvent, showNotification } from "./util"; | |
| 16 | + | import { | |
| 17 | + | ios, | |
| 18 | + | webkit, | |
| 19 | + | href, | |
| 20 | + | useEffectEvent, | |
| 21 | + | showNotification, | |
| 22 | + | serviceWorker, | |
| 23 | + | } from "./util"; | |
| 17 | 24 | import { SettingsContext, SettingsData } from "./components/Settings"; | |
| 18 | 25 | import { Call } from "./components/Call"; | |
| 19 | 26 | import { LoginRequest } from "./components/LoginRequest"; |
| ... | ... | @@ -155,7 +162,7 @@ const AppInner = () => { | |
| 155 | 162 | }, [accounts]); | |
| 156 | 163 | ||
| 157 | 164 | useEffect(() => { | |
| 158 | - | if (!navigator.serviceWorker) return; | |
| 165 | + | if (!serviceWorker) return; | |
| 159 | 166 | const handler = (event: MessageEvent) => { | |
| 160 | 167 | const accountId = event.data?.data?.accountId; | |
| 161 | 168 | const chatId = event.data?.data?.chatId; |
| ... | ... | @@ -187,10 +194,9 @@ const AppInner = () => { | |
| 187 | 194 | } | |
| 188 | 195 | } | |
| 189 | 196 | }; | |
| 190 | - | navigator.serviceWorker.addEventListener("message", handler); | |
| 197 | + | serviceWorker.addEventListener("message", handler); | |
| 191 | 198 | ||
| 192 | - | return () => | |
| 193 | - | navigator.serviceWorker.removeEventListener("message", handler); | |
| 199 | + | return () => serviceWorker.removeEventListener("message", handler); | |
| 194 | 200 | }, [accounts, navigate]); | |
| 195 | 201 | ||
| 196 | 202 | return ( |
+4
-4
src/components/Accounts.tsx
#
| ... | ... | @@ -33,7 +33,7 @@ import { | |
| 33 | 33 | } from "@fortawesome/free-solid-svg-icons"; | |
| 34 | 34 | ||
| 35 | 35 | import { SettingsContext } from "./Settings"; | |
| 36 | - | import { useChats } from "../util"; | |
| 36 | + | import { useChats, readyServiceWorker } from "../util"; | |
| 37 | 37 | ||
| 38 | 38 | export const Accounts = () => { | |
| 39 | 39 | const { accounts, setAccounts, persistence, setSettings } = |
| ... | ... | @@ -144,9 +144,9 @@ export const Accounts = () => { | |
| 144 | 144 | ||
| 145 | 145 | accounts[params.accountId].client.logout(completely); | |
| 146 | 146 | ||
| 147 | - | if (navigator.serviceWorker) { | |
| 148 | - | const serviceWorkerRegistration = await navigator.serviceWorker.ready; | |
| 149 | - | const sub = await serviceWorkerRegistration.pushManager.getSubscription(); | |
| 147 | + | const serviceWorker = await readyServiceWorker; | |
| 148 | + | if (serviceWorker) { | |
| 149 | + | const sub = await serviceWorker.pushManager.getSubscription(); | |
| 150 | 150 | if (!sub) return; | |
| 151 | 151 | await sub.unsubscribe(); // TODO: this isn't per account... | |
| 152 | 152 | } |
+13
-18
src/components/Call.tsx
#
| ... | ... | @@ -27,7 +27,7 @@ import { | |
| 27 | 27 | } from "@fortawesome/free-solid-svg-icons"; | |
| 28 | 28 | ||
| 29 | 29 | import { AvatarWithPlaceholder } from "./AvatarWithPlaceholder"; | |
| 30 | - | import { href, useEffectEvent } from "../util"; | |
| 30 | + | import { href, useEffectEvent, readyServiceWorker } from "../util"; | |
| 31 | 31 | ||
| 32 | 32 | const playTone = (frequency1: number, frequency2: number, duration: number) => { | |
| 33 | 33 | const audioContext = new AudioContext(); |
| ... | ... | @@ -129,10 +129,10 @@ const showCallNotification = async ( | |
| 129 | 129 | ) => { | |
| 130 | 130 | if (!Notification?.permission) return null; | |
| 131 | 131 | ||
| 132 | + | const serviceWorker = await readyServiceWorker; | |
| 132 | 133 | const tag = accountId + ">" + session.get_chatId() + ">call"; | |
| 133 | - | if (navigator.serviceWorker) { | |
| 134 | - | const serviceWorkerRegistration = await navigator.serviceWorker.ready; | |
| 135 | - | const notifs = await serviceWorkerRegistration.getNotifications({ | |
| 134 | + | if (serviceWorker) { | |
| 135 | + | const notifs = await serviceWorker.getNotifications({ | |
| 136 | 136 | tag: tag, | |
| 137 | 137 | }); | |
| 138 | 138 | if (notifs.length > 0) return null; // Already notified, probably from push |
| ... | ... | @@ -152,9 +152,8 @@ const showCallNotification = async ( | |
| 152 | 152 | }; | |
| 153 | 153 | ||
| 154 | 154 | let notification = null; | |
| 155 | - | if (navigator.serviceWorker) { | |
| 156 | - | const serviceWorkerRegistration = await navigator.serviceWorker.ready; | |
| 157 | - | await serviceWorkerRegistration.showNotification(title, options); | |
| 155 | + | if (serviceWorker) { | |
| 156 | + | await serviceWorker.showNotification(title, options); | |
| 158 | 157 | } else { | |
| 159 | 158 | notification = new Notification(title, options); | |
| 160 | 159 |
| ... | ... | @@ -322,20 +321,16 @@ export const Call = ({ | |
| 322 | 321 | })(); | |
| 323 | 322 | }); | |
| 324 | 323 | ||
| 325 | - | const callStatusCleanup = useEffectEvent(() => { | |
| 324 | + | const callStatusCleanup = useEffectEvent(async () => { | |
| 326 | 325 | playingCancelledRef.current = true; | |
| 327 | 326 | stopNowRef.current(); | |
| 328 | - | ||
| 329 | 327 | notificationRef.current?.close(); | |
| 330 | - | if (navigator.serviceWorker) { | |
| 331 | - | (async () => { | |
| 332 | - | const serviceWorkerRegistration = await navigator.serviceWorker.ready; | |
| 333 | - | const notifs = await serviceWorkerRegistration.getNotifications({ | |
| 334 | - | tag: callAccountId + ">" + call?.get_chatId() + ">call", | |
| 335 | - | }); | |
| 336 | - | notifs.forEach((notif) => notif.close()); | |
| 337 | - | })(); | |
| 338 | - | } | |
| 328 | + | const serviceWorker = await readyServiceWorker; | |
| 329 | + | if (!serviceWorker) return; | |
| 330 | + | const notifs = await serviceWorker.getNotifications({ | |
| 331 | + | tag: callAccountId + ">" + call?.get_chatId() + ">call", | |
| 332 | + | }); | |
| 333 | + | notifs.forEach((notif) => notif.close()); | |
| 339 | 334 | }); | |
| 340 | 335 | ||
| 341 | 336 | useEffect(() => { |
+7
-8
src/components/Chat.tsx
#
| ... | ... | @@ -967,14 +968,12 @@ export const ChatInner = () => { | |
| 967 | 968 | setTimeout(async () => { | |
| 968 | 969 | activeChat.markReadUpTo(mostRecentMessage); | |
| 969 | 970 | const tag = client.accountId() + ">" + activeChat.chatId; | |
| 970 | - | if (navigator.serviceWorker) { | |
| 971 | - | const serviceWorkerRegistration = await navigator.serviceWorker.ready; | |
| 972 | - | if (serviceWorkerRegistration.getNotifications) { | |
| 973 | - | const notifs = await serviceWorkerRegistration.getNotifications({ | |
| 974 | - | tag, | |
| 975 | - | }); | |
| 976 | - | notifs.forEach((notif) => notif.close()); | |
| 977 | - | } | |
| 971 | + | const serviceWorker = await readyServiceWorker; | |
| 972 | + | if (serviceWorker?.getNotifications) { | |
| 973 | + | const notifs = await serviceWorker.getNotifications({ | |
| 974 | + | tag, | |
| 975 | + | }); | |
| 976 | + | notifs.forEach((notif) => notif.close()); | |
| 978 | 977 | } | |
| 979 | 978 | }, 0); | |
| 980 | 979 | }, [activeChat, client]); |
+4
-4
src/components/Login.tsx
#
| ... | ... | @@ -15,9 +16,8 @@ const registerPush = ( | |
| 15 | 16 | ||
| 16 | 17 | client.addStatusOnlineListener(() => { | |
| 17 | 18 | (async () => { | |
| 18 | - | if (!navigator.serviceWorker || !Notification?.permission) return; // If no service worker support, don't bother with push | |
| 19 | - | ||
| 20 | - | const serviceWorkerRegistration = await navigator.serviceWorker.ready; | |
| 19 | + | const serviceWorker = await readyServiceWorker; | |
| 20 | + | if (!serviceWorker || !Notification?.permission) return; | |
| 21 | 21 | // public key can be computed from private key, but then we need code to do that, so we store both | |
| 22 | 22 | let key = (await idbp.get("vapid_key")) as { | |
| 23 | 23 | publicKey: ArrayBuffer; |
+4
-3
src/components/LoginRequest.tsx
#
| ... | ... | @@ -15,9 +16,9 @@ const showNotification = async (req: borogove.LoginRequest) => { | |
| 15 | 16 | tag: req.id, | |
| 16 | 17 | }; | |
| 17 | 18 | ||
| 18 | - | if (navigator.serviceWorker) { | |
| 19 | - | const serviceWorkerRegistration = await navigator.serviceWorker.ready; | |
| 20 | - | await serviceWorkerRegistration.showNotification(title, options); | |
| 19 | + | const serviceWorker = await readyServiceWorker; | |
| 20 | + | if (serviceWorker) { | |
| 21 | + | await serviceWorker.showNotification(title, options); | |
| 21 | 22 | } else { | |
| 22 | 23 | new Notification(title, options); | |
| 23 | 24 | } |
+6
-4
src/index.tsx
#
| ... | ... | @@ -92,14 +93,15 @@ root.render( | |
| 92 | 93 | </StrictMode>, | |
| 93 | 94 | ); | |
| 94 | 95 | ||
| 95 | - | if (navigator.serviceWorker) { | |
| 96 | + | if (serviceWorker) { | |
| 97 | + | if (import.meta.env.PROD) serviceWorker.register("/sw.js", { scope: "/" }); | |
| 96 | 98 | (async () => { | |
| 97 | - | const reg = await navigator.serviceWorker.ready; | |
| 98 | - | if (!navigator.serviceWorker.controller) { | |
| 99 | + | const reg = await readyServiceWorker; | |
| 100 | + | if (!serviceWorker.controller) { | |
| 99 | 101 | // There is a service worker but it's not attached | |
| 100 | 102 | // Force attach | |
| 101 | 103 | // Needed at least in Firefox for hard-reload | |
| 102 | - | reg.active?.postMessage("clientsClaim"); | |
| 104 | + | reg?.active?.postMessage("clientsClaim"); | |
| 103 | 105 | } | |
| 104 | 106 | })(); | |
| 105 | 107 | } |
+8
-6
src/util.ts
#
| ... | ... | @@ -3,6 +3,9 @@ import Delta from "quill-delta"; | |
| 3 | 3 | import { href as rhref } from "react-router"; | |
| 4 | 4 | import { useRef, useCallback, useEffect, useSyncExternalStore } from "react"; | |
| 5 | 5 | ||
| 6 | + | export const serviceWorker = navigator.serviceWorker; | |
| 7 | + | export const readyServiceWorker = serviceWorker?.ready ?? Promise.resolve(null); | |
| 8 | + | ||
| 6 | 9 | export const ios = navigator.userAgent.match(/(iPad|iPhone|iPod)/g); | |
| 7 | 10 | export const webkit = | |
| 8 | 11 | navigator.userAgent.match(/AppleWebKit/) && |
| ... | ... | @@ -88,11 +91,11 @@ export const showNotification = async ( | |
| 88 | 91 | ) => { | |
| 89 | 92 | if (!Notification?.permission) return; | |
| 90 | 93 | ||
| 94 | + | const serviceWorker = await readyServiceWorker; | |
| 91 | 95 | const tag = accountId + ">" + message.chatId(); | |
| 92 | - | if (navigator.serviceWorker) { | |
| 96 | + | if (serviceWorker) { | |
| 93 | 97 | // Check if we already notified this message | |
| 94 | - | const serviceWorkerRegistration = await navigator.serviceWorker.ready; | |
| 95 | - | const notifs = await serviceWorkerRegistration.getNotifications({ | |
| 98 | + | const notifs = await serviceWorker.getNotifications({ | |
| 96 | 99 | tag: tag, | |
| 97 | 100 | }); | |
| 98 | 101 | for (const notif of notifs) { |
| ... | ... | @@ -127,9 +130,8 @@ export const showNotification = async ( | |
| 127 | 130 | timestamp: message.timeSent, | |
| 128 | 131 | }, | |
| 129 | 132 | }; | |
| 130 | - | if (navigator.serviceWorker) { | |
| 131 | - | const serviceWorkerRegistration = await navigator.serviceWorker.ready; | |
| 132 | - | await serviceWorkerRegistration.showNotification(title, options); | |
| 133 | + | if (serviceWorker) { | |
| 134 | + | await serviceWorker.showNotification(title, options); | |
| 133 | 135 | } else { | |
| 134 | 136 | const notif = new Notification(title, options); | |
| 135 | 137 | notif.addEventListener("click", () => { |