generated from nhcarrigan/template
1c45507cdf
### Explanation _No response_ ### Issue Closes #102 ### Attestations - [ ] I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/) - [ ] I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/). - [ ] My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/). ### Dependencies - [ ] I have pinned the dependencies to a specific patch version. ### Style - [ ] I have run the linter and resolved any errors. - [ ] My pull request uses an appropriate title, matching the conventional commit standards. - [ ] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request. ### Tests - [ ] My contribution adds new code, and I have added tests to cover it. - [ ] My contribution modifies existing code, and I have updated the tests to reflect these changes. - [ ] All new and existing tests pass locally with my changes. - [ ] Code coverage remains at or above the configured threshold. ### Documentation _No response_ ### Versioning _No response_ Reviewed-on: #103 Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com> Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
128 lines
4.2 KiB
TypeScript
128 lines
4.2 KiB
TypeScript
import { soundPlayer } from "./soundPlayer";
|
|
import { NotificationType, NOTIFICATION_SOUNDS } from "./types";
|
|
import { invoke } from "@tauri-apps/api/core";
|
|
import { sendTerminalNotification } from "./terminalNotifier";
|
|
|
|
class NotificationManager {
|
|
async notify(type: NotificationType, message?: string): Promise<void> {
|
|
// Always play sound (if enabled)
|
|
await soundPlayer.play(type);
|
|
|
|
const sound = NOTIFICATION_SOUNDS[type];
|
|
const title = sound.phrase;
|
|
const body = message || this.getDefaultMessage(type);
|
|
|
|
// Try multiple notification methods in order
|
|
const notificationMethods = [
|
|
// Method 1: Try Windows PowerShell (best for system tray notifications)
|
|
async () => {
|
|
console.log("Trying Windows PowerShell notifications...");
|
|
await invoke("send_windows_notification", { title, body });
|
|
},
|
|
|
|
// Method 2: Try native Windows toast (for Windows builds)
|
|
async () => {
|
|
console.log("Trying native Windows toast...");
|
|
await invoke("send_windows_toast", { title, body });
|
|
},
|
|
|
|
// Method 3: Try WSL-specific notification (Windows toast via PowerShell - for WSL)
|
|
async () => {
|
|
console.log("Trying WSL notification...");
|
|
await invoke("send_wsl_notification", { title, body });
|
|
},
|
|
|
|
// Method 4: Try native Tauri notifications
|
|
async () => {
|
|
console.log("Trying Tauri native notifications...");
|
|
const { sendNotification, isPermissionGranted, requestPermission } =
|
|
await import("@tauri-apps/plugin-notification");
|
|
|
|
let hasPermission = await isPermissionGranted();
|
|
if (!hasPermission) {
|
|
const permission = await requestPermission();
|
|
hasPermission = permission === "granted";
|
|
}
|
|
|
|
if (hasPermission) {
|
|
await sendNotification({ title, body });
|
|
} else {
|
|
throw new Error("Notification permission denied");
|
|
}
|
|
},
|
|
|
|
// Method 5: Try notify-send (for native Linux)
|
|
async () => {
|
|
console.log("Trying notify-send...");
|
|
await invoke("send_notify_send", { title, body });
|
|
},
|
|
|
|
// Skip VBScript and simple message as they create popup dialogs
|
|
// Only use them in the debugger for testing
|
|
];
|
|
|
|
// Try each method until one succeeds
|
|
for (const method of notificationMethods) {
|
|
try {
|
|
await method();
|
|
console.log("Notification sent successfully");
|
|
return; // Success, stop trying other methods
|
|
} catch (error) {
|
|
console.warn("Notification method failed:", error);
|
|
// Continue to next method
|
|
}
|
|
}
|
|
|
|
console.error("All notification methods failed, using terminal notification");
|
|
// Final fallback: Show in terminal
|
|
sendTerminalNotification(type, body);
|
|
}
|
|
|
|
private getDefaultMessage(type: NotificationType): string {
|
|
switch (type) {
|
|
case NotificationType.SUCCESS:
|
|
return "Task completed successfully!";
|
|
case NotificationType.ERROR:
|
|
return "Something went wrong...";
|
|
case NotificationType.PERMISSION:
|
|
return "Permission needed to continue";
|
|
case NotificationType.CONNECTION:
|
|
return "Successfully connected to Claude Code";
|
|
case NotificationType.TASK_START:
|
|
return "Starting task...";
|
|
case NotificationType.COST_ALERT:
|
|
return "You've exceeded your cost threshold!";
|
|
default:
|
|
return "Notification";
|
|
}
|
|
}
|
|
|
|
// Helper methods for common notifications
|
|
async notifySuccess(message?: string): Promise<void> {
|
|
await this.notify(NotificationType.SUCCESS, message);
|
|
}
|
|
|
|
async notifyError(message?: string): Promise<void> {
|
|
await this.notify(NotificationType.ERROR, message);
|
|
}
|
|
|
|
async notifyPermission(message?: string): Promise<void> {
|
|
await this.notify(NotificationType.PERMISSION, message);
|
|
}
|
|
|
|
async notifyConnection(message?: string): Promise<void> {
|
|
await this.notify(NotificationType.CONNECTION, message);
|
|
}
|
|
|
|
async notifyTaskStart(message?: string): Promise<void> {
|
|
await this.notify(NotificationType.TASK_START, message);
|
|
}
|
|
|
|
async notifyCostAlert(message?: string): Promise<void> {
|
|
await this.notify(NotificationType.COST_ALERT, message);
|
|
}
|
|
}
|
|
|
|
// Export singleton instance
|
|
export const notificationManager = new NotificationManager();
|