1import * as Electron from "electron";
2import * as Effect from "effect/Effect";
3import * as Schema from "effect/Schema";
4import * as HostProcess from "@t3tools/shared/HostProcess";
5
6import * as ElectronApp from "../../electron/ElectronApp.ts";
7import * as DesktopIpc from "../DesktopIpc.ts";
8import { SET_NOTIFICATION_BADGE_CHANNEL } from "../channels.ts";
9
10const NotificationBadge = Schema.Struct({
11 count: Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 2_147_483_647 })),
12 image: Schema.NullOr(
13 Schema.String.check(
14 Schema.isMaxLength(16_384),
15 Schema.isPattern(/^data:image\/png;base64,[a-z0-9+/]+={0,2}$/i),
16 ),
17 ),
18});
19
20export function applyNotificationBadge(
21 platform: NodeJS.Platform,
22 { count, image }: typeof NotificationBadge.Type,
23): void {
24 try {
25 if (Electron.BrowserWindow.getFocusedWindow()) count = 0;
26 if (platform === "win32") {
27 const overlay = count > 0 && image ? Electron.nativeImage.createFromDataURL(image) : null;
28 for (const window of Electron.BrowserWindow.getAllWindows()) {
29 if (!window.isDestroyed()) {
30 window.setOverlayIcon(
31 overlay?.isEmpty() ? null : overlay,
32 count > 0 ? `${count} threads with new notifications` : "",
33 );
34 }
35 }
36 } else if (platform === "darwin" || platform === "linux") {
37 Electron.app.setBadgeCount(count);
38 }
39 } catch (error) {
40 Effect.runSync(Effect.logWarning("Could not update notification badge", error));
41 }
42}
43
44export const installNotificationBadge = Effect.fn("desktop.ipc.installNotificationBadge")(
45 function* () {
46 const ipc = yield* DesktopIpc.DesktopIpc;
47 const app = yield* ElectronApp.ElectronApp;
48 const platform = yield* HostProcess.Platform;
49 const clear = () => {
50 applyNotificationBadge(platform, { count: 0, image: null });
51 for (const window of Electron.BrowserWindow.getAllWindows()) {
52 if (!window.isDestroyed()) window.webContents.send(SET_NOTIFICATION_BADGE_CHANNEL);
53 }
54 };
55 yield* ipc.handle(
56 DesktopIpc.makeIpcMethod({
57 channel: SET_NOTIFICATION_BADGE_CHANNEL,
58 payload: NotificationBadge,
59 result: Schema.Void,
60 handler: (badge) =>
61 Effect.sync(() => {
62 if (badge.count > 0 && Electron.BrowserWindow.getFocusedWindow()) clear();
63 else applyNotificationBadge(platform, badge);
64 }),
65 }),
66 );
67 yield* app.on("browser-window-focus", clear);
68 yield* app.on("before-quit", clear);
69 yield* Effect.addFinalizer(() => Effect.sync(clear));
70 },
71);