mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-12 19:55:19 +02:00
fix gc issue
This commit is contained in:
parent
8cb55a903d
commit
8602330bbc
6 changed files with 24 additions and 55 deletions
|
|
@ -24,30 +24,19 @@ export function extractDeepLinkFromArgv(argv: readonly string[]): string | null
|
|||
}
|
||||
|
||||
export function dispatchDeepLink(url: string): void {
|
||||
console.log(`[deeplink] dispatch ${url}`);
|
||||
if (!url.startsWith(URL_PREFIX)) {
|
||||
console.log(`[deeplink] rejected: bad prefix`);
|
||||
return;
|
||||
}
|
||||
if (!url.startsWith(URL_PREFIX)) return;
|
||||
|
||||
pendingUrl = url;
|
||||
|
||||
const win = mainWindowRef;
|
||||
if (!win || win.isDestroyed()) {
|
||||
console.log(`[deeplink] no window, buffered`);
|
||||
return;
|
||||
}
|
||||
if (!win || win.isDestroyed()) return;
|
||||
|
||||
if (win.isMinimized()) win.restore();
|
||||
win.show();
|
||||
win.focus();
|
||||
|
||||
if (win.webContents.isLoading()) {
|
||||
console.log(`[deeplink] window loading, buffered`);
|
||||
return;
|
||||
}
|
||||
if (win.webContents.isLoading()) return;
|
||||
|
||||
console.log(`[deeplink] sending app:openUrl to renderer`);
|
||||
win.webContents.send("app:openUrl", { url });
|
||||
pendingUrl = null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,38 +6,38 @@ const HTTP_URL = /^https?:\/\//i;
|
|||
const ROWBOAT_URL = /^rowboat:\/\//i;
|
||||
|
||||
export class ElectronNotificationService implements INotificationService {
|
||||
// Holds strong references to active Notification instances so the GC can't
|
||||
// collect them while they're still visible — without this, the click handler
|
||||
// gets dropped and macOS clicks just focus the app silently.
|
||||
private active = new Set<Notification>();
|
||||
|
||||
isSupported(): boolean {
|
||||
return Notification.isSupported();
|
||||
}
|
||||
|
||||
notify({ title = "Rowboat", message, link, actionLabel }: NotifyInput): void {
|
||||
notify({ title = "Rowboat", message, link }: NotifyInput): void {
|
||||
const notification = new Notification({
|
||||
title,
|
||||
body: message,
|
||||
// Action button is only meaningful when there's something to open.
|
||||
// macOS shows the first action inline (Banner) or all (Alert).
|
||||
actions: link ? [{ type: "button", text: actionLabel?.trim() || "Open" }] : [],
|
||||
});
|
||||
|
||||
const handleAction = (source: string) => {
|
||||
console.log(`[notification] ${source} fired, link=${link ?? '<none>'}`);
|
||||
this.active.add(notification);
|
||||
const release = () => { this.active.delete(notification); };
|
||||
|
||||
notification.on("click", () => {
|
||||
if (link && ROWBOAT_URL.test(link)) {
|
||||
dispatchDeepLink(link);
|
||||
return;
|
||||
}
|
||||
if (link && HTTP_URL.test(link)) {
|
||||
} else if (link && HTTP_URL.test(link)) {
|
||||
shell.openExternal(link).catch((err) => {
|
||||
console.error("[notification] failed to open link:", err);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
this.focusMainWindow();
|
||||
}
|
||||
this.focusMainWindow();
|
||||
};
|
||||
|
||||
// Both events route through the same handler — body click on macOS is
|
||||
// less reliable than action-button click, but we want either to work.
|
||||
notification.on("click", () => handleAction("click"));
|
||||
notification.on("action", () => handleAction("action"));
|
||||
release();
|
||||
});
|
||||
notification.on("close", release);
|
||||
notification.on("failed", release);
|
||||
|
||||
notification.show();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3097,14 +3097,11 @@ function App() {
|
|||
useEffect(() => {
|
||||
const handle = (url: string) => {
|
||||
const view = parseDeepLink(url)
|
||||
console.log('[deeplink renderer] received', url, '→ view', view)
|
||||
if (view) void navigateToViewRef.current(view)
|
||||
}
|
||||
void window.ipc.invoke('app:consumePendingDeepLink', null).then(({ url }) => {
|
||||
console.log('[deeplink renderer] mount drain:', url)
|
||||
if (url) handle(url)
|
||||
})
|
||||
console.log('[deeplink renderer] listener registered')
|
||||
return window.ipc.on('app:openUrl', ({ url }) => handle(url))
|
||||
}, [])
|
||||
|
||||
|
|
|
|||
|
|
@ -14,15 +14,10 @@ Triggers a native macOS notification. The call returns immediately; it does not
|
|||
### Parameters
|
||||
- **\`title\`** (optional, defaults to \`"Rowboat"\`) — bold headline at the top.
|
||||
- **\`message\`** (required) — body text. Keep it short — macOS truncates after a couple of lines.
|
||||
- **\`link\`** (optional) — URL to open when the user clicks the notification or its action button. Two kinds accepted:
|
||||
- **\`link\`** (optional) — URL to open when the user clicks the notification. Two kinds accepted:
|
||||
- **\`https://...\` / \`http://...\`** — opens in the default browser
|
||||
- **\`rowboat://...\`** — opens a view inside Rowboat (see deep links below)
|
||||
- If omitted, clicking the notification focuses the Rowboat app.
|
||||
- **\`actionLabel\`** (optional, defaults to \`"Open"\`) — label for the inline action button. Only shown when \`link\` is set. Keep it to 1-2 words: \`"Open"\`, \`"View"\`, \`"Read"\`, \`"Reply"\`. Pick a verb that names what clicking will do.
|
||||
|
||||
### Why the action button matters
|
||||
|
||||
When \`link\` is set, an action button is shown inline on the notification (the same way Slack shows "Reply" or Mail shows "Mark as Read"). This button is **the recommended click target** — it's a clear CTA and it's more reliable than expecting the user to click the notification body. Body click also works as a fallback.
|
||||
|
||||
### Examples
|
||||
|
||||
|
|
@ -43,7 +38,7 @@ External link:
|
|||
}
|
||||
\`\`\`
|
||||
|
||||
Deep link into a Rowboat note (default "Open" button):
|
||||
Deep link into a Rowboat note:
|
||||
\`\`\`json
|
||||
{
|
||||
"message": "Daily brief is ready",
|
||||
|
|
@ -51,16 +46,6 @@ Deep link into a Rowboat note (default "Open" button):
|
|||
}
|
||||
\`\`\`
|
||||
|
||||
Custom action label:
|
||||
\`\`\`json
|
||||
{
|
||||
"title": "Stripe charge declined",
|
||||
"message": "Card ending 4242 — retry from the dashboard",
|
||||
"link": "https://dashboard.stripe.com/payments/pi_abc",
|
||||
"actionLabel": "Review"
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
## Deep links: \`rowboat://\`
|
||||
|
||||
Use these as the \`link\` parameter to land the user on a specific view in Rowboat instead of an external site. URL-encode paths/names that contain spaces or special characters.
|
||||
|
|
|
|||
|
|
@ -1524,7 +1524,6 @@ export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
|
|||
link: z.string().url().refine((v) => /^(https?|rowboat):\/\//i.test(v), {
|
||||
message: "link must be an http(s):// or rowboat:// URL",
|
||||
}).optional().describe("Optional URL opened when the user clicks the notification. Accepts http(s):// (opens in browser) or rowboat:// (opens a view inside Rowboat — see the notify-user skill for deep-link shapes)."),
|
||||
actionLabel: z.string().min(1).max(20).optional().describe("Label for the action button shown when `link` is set. Defaults to 'Open'. Keep it short — 1-2 words like 'Open', 'View', 'Read', 'Reply'. Ignored when no link is provided."),
|
||||
}),
|
||||
isAvailable: async () => {
|
||||
try {
|
||||
|
|
@ -1533,13 +1532,13 @@ export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
|
|||
return false;
|
||||
}
|
||||
},
|
||||
execute: async ({ title, message, link, actionLabel }: { title?: string; message: string; link?: string; actionLabel?: string }) => {
|
||||
execute: async ({ title, message, link }: { title?: string; message: string; link?: string }) => {
|
||||
try {
|
||||
const service = container.resolve<INotificationService>('notificationService');
|
||||
if (!service.isSupported()) {
|
||||
return { success: false, error: 'Notifications are not supported on this system' };
|
||||
}
|
||||
service.notify({ title, message, link, actionLabel });
|
||||
service.notify({ title, message, link });
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ export interface NotifyInput {
|
|||
title?: string;
|
||||
message: string;
|
||||
link?: string;
|
||||
actionLabel?: string;
|
||||
}
|
||||
|
||||
export interface INotificationService {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue