cheogram-www

created pr with cheogram-www/tauri-alpha.1 on 2026-10-29T17:17:18Z · by 3c31bc8c
commented on 2026-10-29T17:17:19Z · by 3c31bc8c
Changes are available at https://tangled.org/mrry.band/cheogram-www on branch tauri-alpha.
added cheogram-www/tauri-alpha.2 on 2026-11-04T17:46:08Z · by 3c31bc8c
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 -3
checkout specific patchset revision:
ssh pr.pico.sh pull cheogram-www:tauri-alpha [rev] | git am -3
add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh cheogram-www:tauri-alpha
add comment to patch request:
ssh pr.pico.sh comment cheogram-www:tauri-alpha "lgtm!"

Patchset cheogram-www/tauri-alpha.2 on 2026-11-04T17:46:08Z · commit ce66e8f

Move notifications code to one file
Eric Roberts 2026-11-04T14:34:43Z
I need to refactor the way we actually send notifications for our Tauri
work, and we have a bunch of places doing the same service worker check.
We had one method in util, others in specific components.

I think keeping it together here makes sense for now, until I change my
mind about this later.

This commit simply moves the files and renames functions without
behaviour changes, so that the actual refactoring work will be easier to
read in the diff.
Semantic diff summary
0 added, 9 modified, 0 signature changed, 0 removed across 5 analyzed files
+3 -9 src/App.tsx #
......@@ -13,14 +13,8 @@ import {
1313 useNavigate,
1414 useParams,
1515 } from "react-router";
16-import {
17- ios,
18- webkit,
19- href,
20- useEffectEvent,
21- showNotification,
22- serviceWorker,
23-} from "./util";
16+import { ios, webkit, href, useEffectEvent, serviceWorker } from "./util";
17+import { showMessageNotification } from "./notifications";
2418 import { SettingsContext, SettingsData } from "./components/Settings";
2519 import { Call } from "./components/Call";
2620 import { LoginRequest } from "./components/LoginRequest";
......@@ -108,7 +102,7 @@ const AppInner = () => {
108102 (chat.notifyReply() && message.replyToMessage && members[1]?.isSelf)
109103 ) {
110104 if (settings.audibleNotifications) new Audio("/message.opus").play();
111- await showNotification(
105+ await showMessageNotification(
112106 client.accountId(),
113107 members[0] ?? message.senderMemberStub(),
114108 message,
+1 -48 src/components/Call.tsx #
......@@ -28,6 +28,7 @@ import {
2828
2929 import { AvatarWithPlaceholder } from "./AvatarWithPlaceholder";
3030 import { href, useEffectEvent, readyServiceWorker } from "../util";
31+import { showCallNotification } from "../notifications";
3132
3233 const playTone = (frequency1: number, frequency2: number, duration: number) => {
3334 const audioContext = new AudioContext();
......@@ -122,54 +123,6 @@ const playRingtone = () => {
122123 };
123124 };
124125
125-const showCallNotification = async (
126- accountId: string,
127- session: borogove.calls.Session,
128- callerDisplayName: string,
129-) => {
130- if (!Notification?.permission) return null;
131-
132- const serviceWorker = await readyServiceWorker;
133- const tag = accountId + ">" + session.get_chatId() + ">call";
134- if (serviceWorker) {
135- const notifs = await serviceWorker.getNotifications({
136- tag: tag,
137- });
138- if (notifs.length > 0) return null; // Already notified, probably from push
139- // We could check session id here, but we only do one call at a time in UI anyway
140- }
141- const title = "Incoming Call";
142- const options = {
143- body: "From " + callerDisplayName,
144- tag: tag,
145- vibrate: [50, 100, 50, 100],
146- data: {
147- accountId: accountId,
148- chatId: session.get_chatId(),
149- type: borogove.MessageType.MessageCall,
150- callStatus: "propose",
151- },
152- };
153-
154- let notification = null;
155- if (serviceWorker) {
156- await serviceWorker.showNotification(title, options);
157- } else {
158- notification = new Notification(title, options);
159-
160- let clicked = false;
161- notification.addEventListener("click", () => {
162- clicked = true;
163- session.accept();
164- });
165- notification.addEventListener("close", () => {
166- if (!clicked) session.hangup();
167- });
168- }
169-
170- return notification;
171-};
172-
173126 export const Call = ({
174127 accounts,
175128 }: {
+2 -19 src/components/LoginRequest.tsx #
......@@ -1,4 +1,4 @@
1-import { readyServiceWorker } from "../util";
1+import { showLoginRequestNotification } from "../notifications";
22 import * as borogove from "borogove";
33 import { Paper } from "@mui/material";
44 import Draggable from "react-draggable";
......@@ -7,23 +7,6 @@ import { Button } from "@chatscope/chat-ui-kit-react";
77 import { faGripHorizontal } from "@fortawesome/free-solid-svg-icons";
88 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
99
10-const showNotification = async (req: borogove.LoginRequest) => {
11- if (!Notification?.permission) return null;
12-
13- const title = "Login Request";
14- const options = {
15- body: req.method + " " + req.url,
16- tag: req.id,
17- };
18-
19- const serviceWorker = await readyServiceWorker;
20- if (serviceWorker) {
21- await serviceWorker.showNotification(title, options);
22- } else {
23- new Notification(title, options);
24- }
25-};
26-
2710 export const LoginRequest = ({
2811 accounts,
2912 }: {
......@@ -42,7 +25,7 @@ export const LoginRequest = ({
4225 client.addLoginRequestListener((req) => {
4326 setLoginRequest(req);
4427 setReqAccountId(accountId);
45- showNotification(req);
28+ showLoginRequestNotification(req);
4629 }),
4730 ];
4831 }),
+134 -0 src/notifications.ts #
......@@ -0,0 +1,134 @@
1+import * as borogove from "borogove";
2+import { href, readyServiceWorker } from "./util";
3+
4+export const showMessageNotification = async (
5+ accountId: string,
6+ sender: borogove.Member,
7+ message: borogove.ChatMessage,
8+) => {
9+ if (!Notification?.permission) return;
10+
11+ const serviceWorker = await readyServiceWorker;
12+ const tag = accountId + ">" + message.chatId();
13+ if (serviceWorker) {
14+ // Check if we already notified this message
15+ const notifs = await serviceWorker.getNotifications({
16+ tag: tag,
17+ });
18+ for (const notif of notifs) {
19+ if (notif.data.messageId === (message.serverId || message.localId))
20+ return;
21+ }
22+ }
23+
24+ const title = "Message from " + (sender?.displayName ?? message.chatId());
25+ let icon = sender.photoUri ?? sender.placeholderUri;
26+ if (!icon.startsWith("data:")) {
27+ const response = await fetch(icon);
28+ const mime = response.headers.get("Content-Type");
29+ if (response.ok && mime) {
30+ const buffer = await response.arrayBuffer();
31+ const base64 = btoa(String.fromCharCode(...new Uint8Array(buffer)));
32+ icon = `data:${mime};base64,${base64}`;
33+ }
34+ }
35+ const body = message.body().toPlainText();
36+ const options = {
37+ body: !body && message.attachments.length > 0 ? "Media" : body,
38+ icon,
39+ tag,
40+ renotify: true,
41+ timestamp: new Date(message.timeSent).getTime(),
42+ vibrate: [50, 50, 50],
43+ data: {
44+ accountId,
45+ chatId: message.chatId(),
46+ messageId: message.serverId || message.localId,
47+ timestamp: message.timeSent,
48+ },
49+ };
50+ if (serviceWorker) {
51+ await serviceWorker.showNotification(title, options);
52+ } else {
53+ const notif = new Notification(title, options);
54+ notif.addEventListener("click", () => {
55+ history.pushState(
56+ null,
57+ "",
58+ href("/accounts/:accountId/chats/:chatId", {
59+ accountId: accountId,
60+ chatId: message.chatId(),
61+ }) + `#message:${message.serverId || message.localId}`,
62+ );
63+ window.dispatchEvent(new PopStateEvent("popstate")); // Notify React Router
64+ });
65+ return notif;
66+ }
67+};
68+
69+export const showCallNotification = async (
70+ accountId: string,
71+ session: borogove.calls.Session,
72+ callerDisplayName: string,
73+) => {
74+ if (!Notification?.permission) return null;
75+
76+ const serviceWorker = await readyServiceWorker;
77+ const tag = accountId + ">" + session.get_chatId() + ">call";
78+ if (serviceWorker) {
79+ const notifs = await serviceWorker.getNotifications({
80+ tag: tag,
81+ });
82+ if (notifs.length > 0) return null; // Already notified, probably from push
83+ // We could check session id here, but we only do one call at a time in UI anyway
84+ }
85+ const title = "Incoming Call";
86+ const options = {
87+ body: "From " + callerDisplayName,
88+ tag: tag,
89+ vibrate: [50, 100, 50, 100],
90+ data: {
91+ accountId: accountId,
92+ chatId: session.get_chatId(),
93+ type: borogove.MessageType.MessageCall,
94+ callStatus: "propose",
95+ },
96+ };
97+
98+ let notification = null;
99+ if (serviceWorker) {
100+ await serviceWorker.showNotification(title, options);
101+ } else {
102+ notification = new Notification(title, options);
103+
104+ let clicked = false;
105+ notification.addEventListener("click", () => {
106+ clicked = true;
107+ session.accept();
108+ });
109+ notification.addEventListener("close", () => {
110+ if (!clicked) session.hangup();
111+ });
112+ }
113+
114+ return notification;
115+};
116+
117+export const showLoginRequestNotification = async (
118+ req: borogove.LoginRequest,
119+) => {
120+ if (!Notification?.permission) return null;
121+
122+ const title = "Login Request";
123+ const options = {
124+ body: req.method + " " + req.url,
125+ tag: req.id,
126+ };
127+
128+ const serviceWorker = await readyServiceWorker;
129+ if (serviceWorker) {
130+ await serviceWorker.showNotification(title, options);
131+ } else {
132+ new Notification(title, options);
133+ }
134+};
+0 -65 src/util.ts #
......@@ -84,71 +84,6 @@ export const useChatExtras = (accountId: string) => {
8484 };
8585 };
8686
87-export const showNotification = async (
88- accountId: string,
89- sender: borogove.Member,
90- message: borogove.ChatMessage,
91-) => {
92- if (!Notification?.permission) return;
93-
94- const serviceWorker = await readyServiceWorker;
95- const tag = accountId + ">" + message.chatId();
96- if (serviceWorker) {
97- // Check if we already notified this message
98- const notifs = await serviceWorker.getNotifications({
99- tag: tag,
100- });
101- for (const notif of notifs) {
102- if (notif.data.messageId === (message.serverId || message.localId))
103- return;
104- }
105- }
106-
107- const title = "Message from " + (sender?.displayName ?? message.chatId());
108- let icon = sender.photoUri ?? sender.placeholderUri;
109- if (!icon.startsWith("data:")) {
110- const response = await fetch(icon);
111- const mime = response.headers.get("Content-Type");
112- if (response.ok && mime) {
113- const buffer = await response.arrayBuffer();
114- const base64 = btoa(String.fromCharCode(...new Uint8Array(buffer)));
115- icon = `data:${mime};base64,${base64}`;
116- }
117- }
118- const body = message.body().toPlainText();
119- const options = {
120- body: !body && message.attachments.length > 0 ? "Media" : body,
121- icon,
122- tag,
123- renotify: true,
124- timestamp: new Date(message.timeSent).getTime(),
125- vibrate: [50, 50, 50],
126- data: {
127- accountId,
128- chatId: message.chatId(),
129- messageId: message.serverId || message.localId,
130- timestamp: message.timeSent,
131- },
132- };
133- if (serviceWorker) {
134- await serviceWorker.showNotification(title, options);
135- } else {
136- const notif = new Notification(title, options);
137- notif.addEventListener("click", () => {
138- history.pushState(
139- null,
140- "",
141- href("/accounts/:accountId/chats/:chatId", {
142- accountId: accountId,
143- chatId: message.chatId(),
144- }) + `#message:${message.serverId || message.localId}`,
145- );
146- window.dispatchEvent(new PopStateEvent("popstate")); // Notify React Router
147- });
148- return notif;
149- }
150-};
151-
15287 const negatedScriptRegex = {
15388 Latn: /(?=\p{L})\P{Script=Latin}/u,
15489 Cyrl: /(?=\p{L})\P{Script=Cyrillic}/u,
Back to top