Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/app/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ export const config = new Store<Config>({
"notifications.times": [],
"updates.autoCheck": true,
"updates.showNotifications": true,
"updates.notificationDelay": "next-day",
"blocker.enabled": true,
"blocker.ads": true,
"blocker.tracking": true,
Expand Down Expand Up @@ -304,5 +303,12 @@ export const config = new Store<Config>({
store.delete("resetConfig");
}
},
">3.45.0": (store) => {
// @ts-expect-error: `updates.notificationDelay` has been removed
if (store.has("updates.notificationDelay")) {
// @ts-expect-error
store.delete("updates.notificationDelay");
}
},
},
});
78 changes: 5 additions & 73 deletions packages/app/updater.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,22 @@
import { is } from "@electron-toolkit/utils";
import { ms } from "@meru/shared/ms";
import type { UpdateDownloadedEvent } from "electron-updater";
import { autoUpdater } from "electron-updater";
import { config } from "@/config";
import { ipc } from "./ipc";
import { log } from "./lib/log";
import { main } from "./main";
import { appState } from "./state";

const URGENT_MARKERS = ["<!-- urgent -->", "[urgent]"];

const NOTIFICATION_DELAY_MS = {
immediate: 0,
"few-hours": ms("4h"),
"next-day": ms("1d"),
} as const;

type ReleaseNotes = UpdateDownloadedEvent["releaseNotes"];

function flattenReleaseNotes(releaseNotes: ReleaseNotes) {
if (!releaseNotes) {
return "";
}

if (typeof releaseNotes === "string") {
return releaseNotes;
}

return releaseNotes.map((entry) => entry.note ?? "").join("\n");
}

function isUrgentRelease(releaseNotes: ReleaseNotes) {
const text = flattenReleaseNotes(releaseNotes).toLowerCase();

return URGENT_MARKERS.some((marker) => text.includes(marker));
}

class AppUpdater {
private pendingVersion: string | null = null;
private notifyTimer: NodeJS.Timeout | null = null;

init() {
autoUpdater.logger = log;

if (config.get("updates.showNotifications")) {
autoUpdater.on("update-downloaded", (updateInfo) => {
this.handleUpdateDownloaded(updateInfo);
ipc.renderer.send(
main.window.webContents,
"appUpdater.updateAvailable",
`v${updateInfo.version}`,
);
});
}

Expand Down Expand Up @@ -78,45 +49,6 @@ class AppUpdater {

autoUpdater.quitAndInstall();
}

private handleUpdateDownloaded(updateInfo: UpdateDownloadedEvent) {
const delayMs = NOTIFICATION_DELAY_MS[config.get("updates.notificationDelay")];

if (delayMs === 0 || isUrgentRelease(updateInfo.releaseNotes)) {
this.clearPendingNotification();
this.notifyRenderer(updateInfo.version);

return;
}

this.clearPendingNotification();

this.pendingVersion = updateInfo.version;

this.notifyTimer = setTimeout(() => {
const version = this.pendingVersion;

this.notifyTimer = null;
this.pendingVersion = null;

if (version) {
this.notifyRenderer(version);
}
}, delayMs);
}

private clearPendingNotification() {
if (this.notifyTimer) {
clearTimeout(this.notifyTimer);
this.notifyTimer = null;
}

this.pendingVersion = null;
}

private notifyRenderer(version: string) {
ipc.renderer.send(main.window.webContents, "appUpdater.updateAvailable", `v${version}`);
}
}

export const appUpdater = new AppUpdater();
60 changes: 1 addition & 59 deletions packages/renderer-main/routes/settings/updates.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,8 @@
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@meru/ui/components/field";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@meru/ui/components/select";
import { useConfig, useConfigMutation } from "@meru/shared/renderer/react-query";
import { FieldGroup } from "@meru/ui/components/field";
import { ConfigSwitchField } from "@/components/config-switch-field";
import { Settings, SettingsContent, SettingsHeader, SettingsTitle } from "@/components/settings";

const notificationDelayItems = [
{ value: "next-day", label: "Next day (recommended)" },
{ value: "few-hours", label: "After a few hours" },
{ value: "immediate", label: "Immediately" },
];

export function UpdatesSettings() {
const { config } = useConfig();
const configMutation = useConfigMutation();

if (!config) {
return;
}

return (
<Settings>
<SettingsHeader>
Expand All @@ -48,37 +21,6 @@ export function UpdatesSettings() {
description="Receive notifications when updates are available."
configKey="updates.showNotifications"
/>
<Field>
<FieldContent>
<FieldLabel>Notification Delay</FieldLabel>
<FieldDescription>
Batch rapid back-to-back releases into a single prompt. Urgent updates always notify
immediately.
</FieldDescription>
</FieldContent>
<Select
items={notificationDelayItems}
value={config["updates.notificationDelay"]}
onValueChange={(value) => {
if (value) {
configMutation.mutate({
"updates.notificationDelay": value,
});
}
}}
>
<SelectTrigger>
<SelectValue placeholder="Select when to notify" />
</SelectTrigger>
<SelectContent>
{notificationDelayItems.map(({ value, label }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
</Field>
</FieldGroup>
</SettingsContent>
</Settings>
Expand Down
1 change: 0 additions & 1 deletion packages/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ export type Config = {
"notifications.times": NotificationTime[];
"updates.autoCheck": boolean;
"updates.showNotifications": boolean;
"updates.notificationDelay": "immediate" | "few-hours" | "next-day";
"blocker.enabled": boolean;
"blocker.ads": boolean;
"blocker.tracking": boolean;
Expand Down
Loading