Skip to content

Conversation

@popaprozac
Copy link

@popaprozac popaprozac commented Apr 29, 2025

Description

Backports Notification API from v3-alpha.
Adds runtime functionality. No JS API at the moment, easy to add?
This includes a self-signing build step for dev and build.

I know this is a sizable addition so if we want to move forward with this I am happy to write docs etc

Fixes (#1788)

Type of change

Please select the option that is relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration using wails doctor.

  • Windows
  • macOS
  • Linux

If you checked Linux, please specify the distro and version.

Test Configuration

Please paste the output of wails doctor. If you are unable to run this command, please describe your environment in as much detail as possible.


          Wails Doctor



# Wails
Version  | v2.10.1
Revision | 1fbcd5f255fbed296e22fb51bde54438d442448c
Modified | true


# System
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
| OS           | MacOS                                                                                                                       |
| Version      | 15.4.1                                                                                                                      |
| ID           | 24E263                                                                                                                      |
| Branding     |                                                                                                                             |
| Go Version   | go1.24.1                                                                                                                    |
| Platform     | darwin                                                                                                                      |
| Architecture | arm64                                                                                                                       |
| CPU 1        | Apple M4 Max                                                                                                                |
| CPU 2        | Apple M4 Max                                                                                                                |
| GPU          | Chipset Model: Apple M4 Max Type: GPU Bus: Built-In Total Number of Cores: 32 Vendor: Apple (0x106b) Metal Support: Metal 3 |
| Memory       | 36GB                                                                                                                        |
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

# Dependencies
┌────────────────────────────────────────────────────────────────────────┐
| Dependency                | Package Name | Status    | Version         |
| Xcode command line tools  | N/A          | Installed | 2409            |
| Nodejs                    | N/A          | Installed | 22.14.0         |
| npm                       | N/A          | Installed | 10.9.2          |
| *Xcode                    | N/A          | Installed | 16.2 (16C5032a) |
| *upx                      | N/A          | Available |                 |
| *nsis                     | N/A          | Available |                 |
|                                                                        |
└─────────────────────── * - Optional Dependency ────────────────────────┘

# Diagnosis
Optional package(s) installation details:
  - upx : Available at https://upx.github.io/
  - nsis : More info at https://wails.io/docs/guides/windows-installer/

 SUCCESS  Your system is ready for Wails development!

Checklist:

  • I have updated website/src/pages/changelog.mdx with details of this PR
  • My code follows the general coding style of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Summary by CodeRabbit

  • New Features
    • Cross-platform desktop notifications (macOS, Windows, Linux): initialization, availability/authorization checks, send notifications (actions & inline reply), category management, removal of pending/delivered notifications, and callbacks for user responses.
  • Build
    • macOS builds now perform automatic self-signing for local use.
  • Documentation
    • Added comprehensive guide and runtime reference for the Notifications API.
  • Chores
    • Added an indirect dependency to support notification functionality.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Apr 29, 2025

Warning

Rate limit exceeded

@leaanthony has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 22 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 2783f55 and d14eee4.

📒 Files selected for processing (1)
  • website/docs/reference/runtime/notification.mdx
📝 Walkthrough

Walkthrough

Adds a cross‑platform desktop notifications subsystem: new frontend types and runtime wrappers, platform implementations for macOS (Obj‑C bridge + cgo), Windows (toasts + registry/COM + icon helper), and Linux (D‑Bus), docs, a macOS post‑build codesign step, and an indirect Go dependency.

Changes

Cohort / File(s) Summary
Frontend API & Runtime
v2/internal/frontend/frontend.go, v2/pkg/runtime/notifications.go
Adds notification types (options, actions, categories, responses/results), extends the Frontend interface with notification lifecycle methods, and adds runtime wrapper functions that forward calls to the frontend.
macOS Obj‑C Bridge
v2/internal/frontend/desktop/darwin/Application.h, v2/internal/frontend/desktop/darwin/Application.m, v2/internal/frontend/desktop/darwin/WailsContext.h, v2/internal/frontend/desktop/darwin/WailsContext.m
Adds C/Objective‑C APIs and WailsContext methods for availability/authorization checks, delegate initialization, sending notifications (with/without actions), category management, pending/delivered removal, UNUserNotificationCenterDelegate implementations, extern callbacks, and fixes MessageDialog declaration.
macOS Go Integration (cgo)
v2/internal/frontend/desktop/darwin/notifications.go
Implements Go-side macOS notification lifecycle: cgo bridges and exported callbacks, async result channels with timeouts, authorization request/check, sending (with actions/data), category registration/removal, and response dispatch.
Linux (D‑Bus) Notifications
v2/internal/frontend/desktop/linux/notifications.go
Implements org.freedesktop.Notifications integration: initialization, send (with actions), category persistence, ActionInvoked/NotificationClosed signal handling, and response callback plumbing.
Windows Toasts & Helpers
v2/internal/frontend/desktop/windows/notifications.go, v2/internal/frontend/desktop/windows/winc/icon.go
Implements Windows toast notifications with COM/registry setup, payload encode/decode for activation args, action/reply handling, category persistence, response dispatch, and adds HICON→PNG utility.
Build Pipeline (macOS codesign)
v2/pkg/commands/build/build.go
Adds a macOS post‑build self‑codesign step: runs codesign --force --deep --sign - on compiled binary for Darwin targets.
Dependency
v2/go.mod
Adds indirect dependency git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3.
Docs & Changelog
website/docs/guides/notifications.mdx, website/docs/reference/runtime/notification.mdx, website/src/pages/changelog.mdx
Adds user guide and API reference for Notifications and updates the changelog under Unreleased.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant App as App (Go)
  participant RT as Runtime wrapper
  participant FE as Frontend (platform)
  participant OS as OS Notification Center
  participant CB as Registered callback

  App->>RT: InitializeNotifications / SendNotification(options)
  RT->>FE: Forward call
  alt macOS
    FE->>OS: Schedule UNNotificationRequest (UNUserNotificationCenter)
  else Windows
    FE->>OS: Show toast (COM activation args / registry)
  else Linux
    FE->>OS: Notify via D-Bus org.freedesktop.Notifications
  end
  Note over OS: Notification displayed

  OS-->>FE: Activation / Action / Dismiss / Signal
  FE->>RT: Normalize NotificationResult (async)
  RT->>CB: Invoke registered callback with NotificationResult
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Enhancement, Documentation, Windows, MacOS, Linux, go

Suggested reviewers

  • leaanthony

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description covers the core intent (backporting Notifications API from v3-alpha, adding runtime functionality, self-signing build step, and testing across Windows, macOS, and Linux), but several checklist items are incomplete: changelog.mdx update is unchecked, and documentation updates are marked incomplete. Complete the checklist by updating website/src/pages/changelog.mdx with PR details (partially done) and adding comprehensive documentation for the new notification APIs.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title '[v2] Notifications API' clearly and specifically identifies the main change—adding a Notifications API to v2—without unnecessary clutter or vague terms.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (8)
v2/internal/frontend/desktop/windows/winc/icon.go (2)

22-29: Lazy-loaded proc handles are fine but should be validated

Consider checking Load() errors for user32.dll/gdi32.dll so that a missing API (very old Windows) surfaces as a clear build-time panic instead of an undefined pointer call.


51-75: Minor: enum values already in w32

BI_RGB and DIB_RGB_COLORS are re-declared here. They already exist in w32. Re-using the central definition avoids drift.

v2/internal/frontend/desktop/darwin/Application.m (2)

390-399: Redundant NSString → UTF8 round-trip can be avoided

identifier, title, subtitle, body and data_json are already passed in as const char *.
Re-encoding them into NSString only to convert them straight back to char * incurs an unnecessary allocation and copies.
It also keeps an autoreleased NSString alive only long enough for the synchronous call, which is safe but brittle if the implementation ever becomes async.

-    NSString *_identifier = safeInit(identifier);
-    NSString *_title = safeInit(title);
-    NSString *_subtitle = safeInit(subtitle);
-    NSString *_body = safeInit(body);
-    NSString *_data_json = safeInit(data_json);
-
-    [ctx SendNotification:channelID :[_identifier UTF8String] :[_title UTF8String] :[_subtitle UTF8String] :[_body UTF8String] :[_data_json UTF8String]];
+    // Pass the original C strings straight through – WailsContext
+    // converts them to NSString internally.
+    [ctx SendNotification:channelID
+                     :identifier
+                     :title
+                     :subtitle
+                     :body
+                     :data_json];

401-411: Same round-trip issue for SendNotificationWithActions

The exact optimisation from the previous comment applies here as well.

-    NSString *_identifier = safeInit(identifier);
-    NSString *_title      = safeInit(title);
-    NSString *_subtitle   = safeInit(subtitle);
-    NSString *_body       = safeInit(body);
-    NSString *_categoryId = safeInit(categoryId);
-    NSString *_actions_json = safeInit(actions_json);
-
-    [ctx SendNotificationWithActions:channelID :[_identifier UTF8String] :[_title UTF8String] :[_subtitle UTF8String] :[_body UTF8String] :[_categoryId UTF8String] :[_actions_json UTF8String]];
+    [ctx SendNotificationWithActions:channelID
+                     :identifier
+                     :title
+                     :subtitle
+                     :body
+                     :categoryId
+                     :actions_json];
v2/internal/frontend/desktop/darwin/WailsContext.m (1)

793-799: Duplicate onceToken shadows the global symbol

A file-scope static dispatch_once_t onceToken is declared at line 793 and a second
function-scope variable with the same name is redeclared inside EnsureDelegateInitialized
(lines 798-799).
The inner declaration hides the outer one, so the outer variable is effectively unused
(code smell / reader confusion).

Remove the global or rename one of them to make intent explicit.

- static dispatch_once_t onceToken;
...
-        static dispatch_once_t onceToken;
+ static dispatch_once_t notificationDelegateOnce;
...
+        static dispatch_once_t notificationDelegateOnce;
v2/pkg/runtime/notifications.go (1)

25-33: Variable shadowing the imported package name

Inside every helper you create a variable called frontend, masking the imported
package frontend.
While legal, this hurts readability and can trip up IDE imports.

-	frontend := getFrontend(ctx)
-	return frontend.InitializeNotifications()
+	fe := getFrontend(ctx)
+	return fe.InitializeNotifications()

Apply consistently to all helpers in this file.

v2/internal/frontend/desktop/darwin/notifications.go (1)

402-424: Close and recycle per-request channels to avoid leaks

registerChannel allocates an entry in the global channels map, but the channel is only removed (and never closed) in GetChannel.
For successful requests the receiver keeps the channel indefinitely, which slowly leaks memory.

After the sending goroutine finishes reading from resultCh, close it:

// Example after select { case result := <-resultCh: … }
-	return nil
+	close(resultCh)
+	return nil

and remove the redundant cleanupChannel helper (or use it consistently for both timeout and success paths).

v2/internal/frontend/desktop/windows/notifications.go (1)

180-188: Avoid iterating over an empty category when none is found

When the requested category is absent the code warns but still iterates over nCategory.Actions, which is the zero value slice.
While harmless, an early return keeps intent clearer:

if options.CategoryID == "" || !categoryExists {
    fmt.Printf("Category '%s' not found, sending basic notification without actions\n", options.CategoryID)
    return f.SendNotification(options)
}

Also applies to: 195-200

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d1838f4 and fee2570.

⛔ Files ignored due to path filters (1)
  • v2/go.sum is excluded by !**/*.sum
📒 Files selected for processing (13)
  • v2/go.mod (1 hunks)
  • v2/internal/frontend/desktop/darwin/Application.h (1 hunks)
  • v2/internal/frontend/desktop/darwin/Application.m (1 hunks)
  • v2/internal/frontend/desktop/darwin/WailsContext.h (1 hunks)
  • v2/internal/frontend/desktop/darwin/WailsContext.m (4 hunks)
  • v2/internal/frontend/desktop/darwin/notifications.go (1 hunks)
  • v2/internal/frontend/desktop/linux/frontend.go (1 hunks)
  • v2/internal/frontend/desktop/linux/notifications.go (1 hunks)
  • v2/internal/frontend/desktop/windows/notifications.go (1 hunks)
  • v2/internal/frontend/desktop/windows/winc/icon.go (2 hunks)
  • v2/internal/frontend/frontend.go (2 hunks)
  • v2/pkg/commands/build/build.go (2 hunks)
  • v2/pkg/runtime/notifications.go (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
v2/pkg/runtime/notifications.go (2)
v2/internal/frontend/frontend.go (5)
  • NotificationOptions (80-87)
  • NotificationAction (90-94)
  • NotificationCategory (97-103)
  • NotificationResponse (106-115)
  • NotificationResult (119-122)
v2/internal/frontend/desktop/darwin/Application.h (11)
  • IsNotificationAvailable (73-73)
  • RequestNotificationAuthorization (76-76)
  • CheckNotificationAuthorization (77-77)
  • SendNotification (78-78)
  • SendNotificationWithActions (79-79)
  • RegisterNotificationCategory (80-80)
  • RemoveNotificationCategory (81-81)
  • RemoveAllPendingNotifications (82-82)
  • RemovePendingNotification (83-83)
  • RemoveAllDeliveredNotifications (84-84)
  • RemoveDeliveredNotification (85-85)
v2/internal/frontend/desktop/windows/winc/icon.go (1)
v2/internal/frontend/desktop/windows/winc/w32/typedef.go (1)
  • HBITMAP (191-191)
v2/internal/frontend/desktop/darwin/notifications.go (4)
v2/internal/frontend/frontend.go (5)
  • Frontend (124-203)
  • NotificationResult (119-122)
  • NotificationOptions (80-87)
  • NotificationCategory (97-103)
  • NotificationResponse (106-115)
v2/internal/frontend/desktop/darwin/Application.h (13)
  • IsNotificationAvailable (73-73)
  • EnsureDelegateInitialized (75-75)
  • CheckBundleIdentifier (74-74)
  • RequestNotificationAuthorization (76-76)
  • CheckNotificationAuthorization (77-77)
  • SendNotification (78-78)
  • SendNotificationWithActions (79-79)
  • RegisterNotificationCategory (80-80)
  • RemoveNotificationCategory (81-81)
  • RemoveAllPendingNotifications (82-82)
  • RemovePendingNotification (83-83)
  • RemoveAllDeliveredNotifications (84-84)
  • RemoveDeliveredNotification (85-85)
v2/internal/frontend/desktop/windows/notifications.go (1)
  • DefaultActionIdentifier (37-37)
v2/internal/frontend/desktop/linux/notifications.go (1)
  • DefaultActionIdentifier (44-44)
🔇 Additional comments (12)
v2/go.mod (1)

54-54: New Windows-toast dependency looks good

go-toast/v2 v2.0.3 is the latest stable tag and is pulled in indirectly, which is correct because the package is only referenced from the Windows implementation.
No further action required.

v2/internal/frontend/desktop/linux/frontend.go (1)

7-7: Whitespace fix prevents CGO mis-parsing

Removing the trailing space from the #cgo directive eliminates a subtle compile warning/error on some tool-chains. Good catch.

v2/internal/frontend/desktop/darwin/Application.h (2)

72-86: Unify bool return-value style with existing API

Earlier query functions (e.g. IsFullScreen, IsMinimised) return const bool, but the newly-added notification functions return bool.
Mixing the two styles will eventually confuse users of the C interface and can trigger C++ linkage warnings.

-bool IsNotificationAvailable(void *inctx);
-
-bool EnsureDelegateInitialized(void *inctx);
+const bool IsNotificationAvailable(void *inctx);
+
+const bool EnsureDelegateInitialized(void *inctx);

Same for the other boolean return types in this block.
[ suggest_nitpick ]


78-80: Parameter order inconsistency may bite the cgo layer

The two “send” helpers differ only by the extra categoryId and actions_json parameters, but the new parameters are inserted before data_json, shifting the tail of the signature.
If any code paths conditionally call one variant over the other through unsafe.Pointer/syscall.NewCallback, a silent mismatch becomes a runtime memory-smash.

Consider re-ordering so that the common tail (data_json) is always last:

-void SendNotificationWithActions(void *inctx, … const char *body, const char *categoryId, const char *actions_json);
+void SendNotificationWithActions(void *inctx, … const char *categoryId, const char *actions_json, const char *body);

Or introduce a distinct name such as SendNotificationWithActionsAndData to make the difference obvious.
[ flag_critical_issue ]

v2/internal/frontend/frontend.go (4)

79-87: NotificationOptions.ID lacks “omitempty” – may break marshalled payloads

All other optional fields in NotificationOptions carry omitempty, but ID does not.
If callers omit it intentionally, the JSON payload will still include "id":"", which some platforms treat as non-empty and may reject.

-	ID         string                 `json:"id"`
+	ID         string                 `json:"id,omitempty"`

[ flag_critical_issue ]


98-103: JSON tag drift: categoryIdentifier vs categoryId

NotificationResponse.CategoryID uses the tag categoryIdentifier, whereas everywhere else the camel-cased form is categoryId.
Apart from being inconsistent, a Go ↔ JS round-trip will lose data unless the client is aware of both spellings.

-	CategoryID       string                 `json:"categoryIdentifier,omitempty"`
+	CategoryID       string                 `json:"categoryId,omitempty"`

[ suggest_essential_refactor ]


189-203: No way to detach OnNotificationResponse callback – memory leak / race risk

The interface lets clients register a callback but never unregister it.
Long-running apps that hot-reload or re-initialise front-ends will accumulate dangling closures, and concurrent writes to the stored function can race.

Suggestion:

  1. Return an opaque token (e.g. func()) that removes the handler.
  2. Document that passing nil replaces & clears the existing one.
-OnNotificationResponse(callback func(result NotificationResult))
+OnNotificationResponse(callback func(result NotificationResult)) (dispose func())

[ suggest_essential_refactor ]


199-203: RemoveNotification duplicates existing concepts – clarify or drop

Both RemovePendingNotification and RemoveDeliveredNotification exist, yet an additional generic RemoveNotification is declared with no semantics stated.
Unless it is meant to be cross-platform sugar, keep only the explicit variants to avoid ambiguity.

[ request_verification ]

v2/internal/frontend/desktop/darwin/WailsContext.h (3)

95-95: Great catch – method now correctly declared as Objective-C instance method
No further action needed.
[ approve_code_changes ]


99-112: Delegate initialisation race: expose idempotent guard or document contract

EnsureDelegateInitialized suggests callers must invoke it before any other notification API, yet nothing enforces this ordering from the Go side.
If two goroutines attempt to send the very first notification concurrently, the internal UNUserNotificationCenter delegate setup could run twice.

Either:

  1. Make the call idempotent and safe internally (preferred), or
  2. Document clearly that InitializeNotifications() must precede everything else.

[ offer_architecture_advice ]


104-107: UTF-8 expectation of const char *…JSON parameters is undocumented

These methods accept raw UTF-8 C strings meant to carry JSON blobs; passing non-UTF-8 or overly long data will break -[NSString stringWithUTF8String:] and return nil, leading to undefined behaviour later.

Add explicit comment or runtime check that returns an error back through the callback path when JSON decoding fails.
[ suggest_optional_refactor ]

v2/internal/frontend/desktop/darwin/WailsContext.m (1)

1010-1057: RemoveNotificationCategory callback executed off main thread

UNUserNotificationCenter invokes the completion handler on an internal queue.
captureResult ultimately crosses the C-go boundary – ensure it is thread-safe or
dispatch back to the main queue to match the rest of the runtime callbacks.

    [center getNotificationCategoriesWithCompletionHandler:^(NSSet<UNNotificationCategory *> *categories) {
        dispatch_async(dispatch_get_main_queue(), ^{
            /* existing logic + captureResult */
        });
    }];

Comment on lines 850 to 861
- (UNMutableNotificationContent *)createNotificationContentWithTitle:(const char *)title subtitle:(const char *)subtitle body:(const char *)body dataJSON:(const char *)dataJSON error:(NSError **)contentError API_AVAILABLE(macos(10.14)) {
NSString *nsTitle = [NSString stringWithUTF8String:title];
NSString *nsSubtitle = subtitle ? [NSString stringWithUTF8String:subtitle] : @"";
NSString *nsBody = [NSString stringWithUTF8String:body];

UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = nsTitle;
if (![nsSubtitle isEqualToString:@""]) {
content.subtitle = nsSubtitle;
}
content.body = nsBody;
content.sound = [UNNotificationSound defaultSound];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Nil-safety for required fields

title and body are required by the macOS API, but stringWithUTF8String: will
crash if a NULL pointer slips through.
Defensive guards make the bridge more robust to future changes in the Go layer.

-    NSString *nsTitle   = [NSString stringWithUTF8String:title];
+    if (title == NULL)  title  = "";
+    if (body  == NULL)  body   = "";
+
+    NSString *nsTitle   = [NSString stringWithUTF8String:title];

Comment on lines +27 to +28
cancel context.CancelFunc
)
Copy link
Contributor

@coderabbitai coderabbitai bot Apr 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

cancel context is created but never used – resource/leak risk

A context.WithCancel is allocated (signalCtx, cancel = …) yet cancel() is never invoked nor returned to the caller.
This leaves the attached goroutine (handleSignals) running for the lifetime of the process and makes clean shutdown impossible.

Consider returning the cancel function (or an explicit Close() method) so callers can terminate signal handling when the frontend is torn down.

Also applies to: 65-70


🏁 Script executed:

#!/bin/bash
set -e
# Locate context.WithCancel usage
grep -R "context.WithCancel" -n v2/internal/frontend/desktop/linux/notifications.go

# Locate cancel variable assignment
grep -R "cancel =" -n v2/internal/frontend/desktop/linux/notifications.go

# Locate handleSignals invocation
grep -R "handleSignals" -n v2/internal/frontend/desktop/linux/notifications.go

# Check if cancel() is ever called
grep -R "cancel()" -n v2/internal/frontend/desktop/linux/notifications.go || echo "No cancel() invocation found"

Length of output: 633


Ensure cancel() is invoked and signalCtx is used for proper shutdown

The signalCtx, cancel = context.WithCancel(...) at line 66 in
v2/internal/frontend/desktop/linux/notifications.go creates a cancelable context that’s never used or cancelled. As a result, the goroutine in handleSignals runs for the process lifetime, leaking resources and preventing a clean shutdown.

• v2/internal/frontend/desktop/linux/notifications.go:66 — assignment of signalCtx, cancel
• v2/internal/frontend/desktop/linux/notifications.go:424 — invocation of handleSignals(ctx, c)
• No cancel() call elsewhere in the file

Suggested changes:

--- a/v2/internal/frontend/desktop/linux/notifications.go
+++ b/v2/internal/frontend/desktop/linux/notifications.go
@@ 66,7c66,8
-   signalCtx, cancel = context.WithCancel(context.Background())
+   signalCtx, cancel = context.WithCancel(context.Background())
+   go f.handleSignals(signalCtx, c)
@@ 420,424c421,425
-   go f.handleSignals(ctx, c)
+   // use the cancelable context for signal handling
+   go f.handleSignals(signalCtx, c)

And add a cleanup path (e.g., in your Close() or teardown method):

func (f *Frontend) Close() {
    // … other cleanup …
    cancel()
}

By returning or storing the cancel function, callers can terminate handleSignals and avoid leaking the goroutine.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am porting this from the v3-alpha implementation where we create services with ServiceShutdown methods. In v2 how do we handle teardown properly?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@popaprozac From my research of the v2 codebase, there isn't a standardized ServiceShutdown pattern like in v3-alpha. Here's how I suggest handling the teardown in v2:

  1. Add a CleanupNotifications() method to your Frontend implementation:
// CleanupNotifications cleans up notification resources
func (f *Frontend) CleanupNotifications() {
    if cancel != nil {
        cancel()
        cancel = nil
    }
    
    if conn != nil {
        conn.Close()
        conn = nil
    }
}
  1. In v2, you'll need to hook this into the application shutdown flow manually. You have a few options:

    a) If this is being used in an application, you can expose a public Cleanup() method that app developers can call when shutting down their app.

    b) You could add a call to a runtime event system (if available) to listen for application shutdown.

The key is that you need to add the cleanup method and document how developers should use it to ensure proper resource cleanup.

For now, I suggest implementing the CleanupNotifications() method at minimum, which can later be hooked into the app shutdown flow as appropriate.


🏁 Script executed:

#!/bin/bash
# Let's check if there's a shutdown or close mechanism in the v2 frontend implementation
echo "Searching for shutdown or close methods in frontend implementations:"
echo "======================================================================"
rg -A 5 "(Shutdown|Close|shutdown|close)" v2/internal/frontend/ -t go

echo -e "\nChecking for app lifecycle methods:"
echo "======================================================================"
rg -A 5 "func (\w+) .*App" v2/internal/frontend -t go

echo -e "\nChecking for any cleanup methods or patterns in frontend implementations:"
echo "======================================================================"
rg -A 5 "(cleanup|Cleanup|destroy|Destroy)" v2/internal/frontend -t go

echo -e "\nLooking at context cancellation patterns in v2:"
echo "======================================================================"
rg "context\.WithCancel" v2/ -t go -A 5

Length of output: 48375


🏁 Script executed:

#!/bin/bash
# Let's look at the Linux frontend structure
echo "Linux frontend structure:"
echo "======================================================================"
rg -l "func \(\w+ \*Frontend\)" v2/internal/frontend/desktop/linux/ -t go | xargs cat

echo -e "\nChecking if the Windows and macOS frontends have shutdown patterns:"
echo "======================================================================"
rg "(Shutdown|Close|cleanup|destroy)" v2/internal/frontend/desktop/(windows|darwin) -t go -A 3

echo -e "\nLooking at how app shutdown is handled in v2:"
echo "======================================================================"
rg "Shutdown" v2/pkg/application/app.go -A 10

Length of output: 35679


🏁 Script executed:

#!/bin/bash
# Let's check the application startup and shutdown sequence
echo "Application startup:"
echo "======================================================================"
rg "func \(\w+ \*App\) Run" v2/pkg/application -A 10

echo -e "\nApplication shutdown:"
echo "======================================================================"
rg "func \(\w+ \*App\) Shutdown" v2/pkg/application -A 15

echo -e "\nFrontend initialization:"
echo "======================================================================"
rg "NewFrontend" v2/internal/frontend -A 5

Length of output: 4557


🏁 Script executed:

#!/bin/bash
# Let's look more closely at the Linux frontend
echo "Linux frontend initialization:"
echo "======================================================================"
rg "func (f \*Frontend) Initialise" v2/internal/frontend/desktop/linux -A 10

echo -e "\nApplication shutdown hooks into frontend:"
echo "======================================================================"
rg "frontend.*Shutdown|frontend.*Close" v2/pkg/application -A 5

# Let's see how other resources are cleaned up in the frontend
echo -e "\nOther resource cleanup in Linux frontend:"
echo "======================================================================"
rg "var conn" v2/internal/frontend/desktop/linux -A 5 -B 5

Length of output: 921


@popaprozac After reviewing the v2 codebase, I can see that unlike v3-alpha which uses a ServiceShutdown pattern, v2 doesn't have a standardized shutdown mechanism for frontend implementations.

For your Linux notifications implementation, you should add a CleanupNotifications() method that will be responsible for properly releasing resources:

// CleanupNotifications cleans up notification resources
func (f *Frontend) CleanupNotifications() {
    if cancel != nil {
        cancel()
        cancel = nil
    }
    
    if conn != nil {
        conn.Close()
        conn = nil
    }
}

Since there isn't a standard place in v2 to hook this teardown, you have a few options:

  1. Expose this method so application developers can call it manually when shutting down
  2. Add a runtime wrapper in v2/pkg/runtime/notifications.go like:
    func CleanupNotifications(ctx context.Context) {
        frontend := getFrontend(ctx)
        if linuxFrontend, ok := frontend.(*linux.Frontend); ok {
            linuxFrontend.CleanupNotifications()
        }
    }

The important thing is to ensure the cancel() function gets called to stop the goroutine and properly clean up the D-Bus connection.


🧬 Code Graph Analysis Results

Relevant snippets from v2/internal/frontend/desktop/linux/notifications.go (full file provided)

  • The file defines a Frontend receiver with methods to initialize, send, and manage notifications on Linux using D-Bus.
  • It uses a package-level cancel variable of type context.CancelFunc which is assigned in InitializeNotifications() when creating a cancellable context for signal handling:
    var cancel context.CancelFunc
    ...
    signalCtx, cancel = context.WithCancel(context.Background())
    ...
    go f.handleSignals(signalCtx, c)
  • The handleSignals method listens for D-Bus signals until the context is done.
  • There is no explicit ServiceShutdown or teardown method in this file.
  • The cancel function is stored globally and can be called to stop signal handling goroutine.
  • Notifications are tracked in a map with locking, and there are methods to remove notifications.
  • The InitializeNotifications() method sets up the D-Bus connection and signal handling.

Relevant snippet from v2/internal/frontend/frontend.go (lines 124-203)

Frontend interface {
	...
	// Notifications
	InitializeNotifications() error
	IsNotificationAvailable() bool
	RequestNotificationAuthorization() (bool, error)
	CheckNotificationAuthorization() (bool, error)
	OnNotificationResponse(callback func(result NotificationResult))
	SendNotification(options NotificationOptions) error
	SendNotificationWithActions(options NotificationOptions) error
	RegisterNotificationCategory(category NotificationCategory) error
	RemoveNotificationCategory(categoryID string) error
	RemoveAllPendingNotifications() error
	RemovePendingNotification(identifier string) error
	RemoveAllDeliveredNotifications() error
	RemoveDeliveredNotification(identifier string) error
	RemoveNotification(identifier string) error
}
  • This interface defines the notification-related methods implemented by platform-specific frontends.
  • There is no explicit Shutdown or ServiceShutdown method here.

Relevant snippet from v2/pkg/runtime/notifications.go (lines 25-93)

func InitializeNotifications(ctx context.Context) error {
	frontend := getFrontend(ctx)
	return frontend.InitializeNotifications()
}

func OnNotificationResponse(ctx context.Context, callback func(result frontend.NotificationResult)) {
	frontend := getFrontend(ctx)
	frontend.OnNotificationResponse(callback)
}

// Other notification functions delegate to frontend implementations.
  • This package provides runtime wrappers that call the frontend's notification methods.
  • No explicit shutdown or teardown function is exposed here.

Summary of relevant information for teardown in v2 Linux notifications:

  • The Linux notifications implementation uses a cancellable context (cancel function) to control the lifetime of the signal handling goroutine.
  • The cancel function is assigned in InitializeNotifications() but there is no explicit method exposed to call it for teardown.
  • The Frontend interface and runtime notification wrappers do not define a Shutdown or ServiceShutdown method.
  • Proper teardown in v2 likely involves calling the stored cancel() function to stop signal handling and cleaning up resources, but this must be done explicitly by the caller or by adding a teardown method.
  • No automatic or built-in teardown method is present in the provided code.

[no_relevant_code_snippets beyond those above]


✏️ Learnings added
Learnt from: popaprozac
PR: wailsapp/wails#4256
File: v2/internal/frontend/desktop/linux/notifications.go:27-28
Timestamp: 2025-04-29T23:54:07.461Z
Learning: In Wails v2, unlike v3-alpha which has a `ServiceShutdown` method for services, there is no standardized teardown pattern for frontend implementations. When implementing features that require cleanup (like goroutines or resources), add explicit cleanup methods (e.g., `CleanupNotifications()`) that handle resource release, context cancellation, and connection closure.

Learnt from: popaprozac
PR: wailsapp/wails#4256
File: v2/internal/frontend/desktop/linux/notifications.go:27-28
Timestamp: 2025-04-29T23:54:07.461Z
Learning: In Wails v2, unlike v3-alpha which has a `ServiceShutdown` method for services, there is no standardized teardown pattern for frontend implementations. When implementing features that require cleanup (like goroutines or resources), add explicit cleanup methods (e.g., `CleanupNotifications()`) that handle resource release, context cancellation, and connection closure.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
v2/internal/frontend/desktop/darwin/WailsContext.m (1)

847-858: Nil-safety for required fields

title and body are required by the macOS API, but stringWithUTF8String: will crash if a NULL pointer slips through.
Defensive guards make the bridge more robust to future changes in the Go layer.

-    NSString *nsTitle = [NSString stringWithUTF8String:title];
+    if (title == NULL)  title  = "";
+    if (body  == NULL)  body   = "";
+
+    NSString *nsTitle = [NSString stringWithUTF8String:title];
v2/internal/frontend/desktop/windows/notifications.go (1)

299-301: ⚠️ Potential issue

saveIconToDir() is still a no-op – Windows toasts will silently fall back to a generic icon

The unresolved stub was pointed out in the previous review and is still present. Without writing the icon file, go-toast cannot embed the application icon in notifications, resulting in a degraded UX.

Proposed implementation (imports and handle‐leak fixes included):

@@
-import (
+import (
   "encoding/base64"
   "encoding/json"
   "runtime"
   "sync"
+  "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
+  "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
 )
@@
 func (f *Frontend) saveIconToDir() error {
-    return nil
+    // Fast-path: file already exists
+    if _, err := os.Stat(iconPath); err == nil {
+        return nil
+    }
+
+    hMod := w32.GetModuleHandle("")
+    if hMod == 0 {
+        return fmt.Errorf("GetModuleHandle failed: %w", syscall.GetLastError())
+    }
+
+    icon, err := winc.NewIconFromResource(hMod, uint16(3)) // 3 is conventional for app icon
+    if err != nil {
+        return fmt.Errorf("failed to retrieve application icon: %w", err)
+    }
+    defer icon.Destroy()
+
+    if err := winc.SaveHIconAsPNG(icon.Handle(), iconPath); err != nil {
+        return fmt.Errorf("failed to save icon as PNG: %w", err)
+    }
+    return nil
 }
🧹 Nitpick comments (2)
v2/internal/frontend/desktop/darwin/WailsContext.m (1)

876-911: Consider refactoring notification sending methods to reduce duplication.

The SendNotification and SendNotificationWithActions methods contain significant duplicate code. Consider extracting the common logic into a shared private helper method to improve maintainability.

+- (void) sendNotificationWithRequest:(UNNotificationRequest *)request channelID:(int)channelID API_AVAILABLE(macos(10.14)) {
+    UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+    [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
+        if (error) {
+            NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]];
+            captureResult(channelID, false, [errorMsg UTF8String]);
+        } else {
+            captureResult(channelID, true, NULL);
+        }
+    }];
+}

Then update the sending methods to use this helper:

 UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:nsIdentifier
                                                                       content:content
                                                                       trigger:trigger];
-    
-[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
-    if (error) {
-        NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]];
-        captureResult(channelID, false, [errorMsg UTF8String]);
-    } else {
-        captureResult(channelID, true, NULL);
-    }
-}];
+[self sendNotificationWithRequest:request channelID:channelID];

Also applies to: 913-951

v2/internal/frontend/desktop/windows/notifications.go (1)

150-152: Replace scattered fmt.Printf diagnostics with structured logging

Direct fmt.Printf writes go to stdout and are easy to miss or spam CI logs. Consider the standard log package (or the project’s logger) with severity levels so messages can be filtered and redirected.

Also applies to: 176-178, 185-186, 409-410

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fee2570 and 35eff5c.

📒 Files selected for processing (6)
  • v2/internal/frontend/desktop/darwin/Application.m (1 hunks)
  • v2/internal/frontend/desktop/darwin/WailsContext.m (4 hunks)
  • v2/internal/frontend/desktop/linux/notifications.go (1 hunks)
  • v2/internal/frontend/desktop/windows/notifications.go (1 hunks)
  • v2/pkg/commands/build/build.go (2 hunks)
  • v2/pkg/runtime/notifications.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • v2/internal/frontend/desktop/darwin/Application.m
🚧 Files skipped from review as they are similar to previous changes (3)
  • v2/pkg/commands/build/build.go
  • v2/internal/frontend/desktop/linux/notifications.go
  • v2/pkg/runtime/notifications.go
🔇 Additional comments (9)
v2/internal/frontend/desktop/darwin/WailsContext.m (8)

40-47: Clean import and declaration setup for notification support.

The code correctly adds conditional imports for UserNotifications framework with macOS version checks and declares the necessary external functions for callback handlers.


721-735: Good platform compatibility and environment checks.

The code appropriately checks for both macOS version compatibility (10.14+) and valid bundle identifier, which are both requirements for the notifications API to function properly.


737-791: Well-implemented notification delegate methods.

The UNUserNotificationCenterDelegate protocol implementation is thorough, with proper handling of notification presentation and user responses. The response payload construction captures all relevant notification data and includes proper error handling for JSON serialization failures.


793-804: Thread-safe delegate initialization.

Using dispatch_once for notification center delegate setup ensures thread safety and prevents multiple initializations, which is an excellent practice.


805-845: Comprehensive authorization handling.

The authorization methods include robust error propagation and appropriate version checking, ensuring failures are properly communicated back to the Go layer.


953-1029: Comprehensive notification category handling.

The category registration implementation properly handles action settings, including text input fields, destructive actions, and deals with updating existing categories. The code shows good attention to detail with effective error handling.


1031-1078: Clean notification management methods.

The notification removal methods provide a complete API surface for managing both pending and delivered notifications, with both individual and bulk removal options.


1089-1089: Minor spacing correction in About method declaration.

Small formatting improvement for consistency.

v2/internal/frontend/desktop/windows/notifications.go (1)

354-360: Shadowing bug fixed – categories now deserialise correctly

Good catch renaming the temporary map and assigning it back; this removes the previous logic error. 👍

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
v2/internal/frontend/desktop/darwin/WailsContext.m (1)

847-851: Nil-safety for required fields (duplicate of prior feedback)

title and body may arrive as NULL; stringWithUTF8String: will crash in that case.
Add guards similar to what you already do for subtitle.

🧹 Nitpick comments (1)
v2/internal/frontend/desktop/darwin/WailsContext.m (1)

740-748: Provide a sensible presentation fallback for macOS < 11

options remains 0 when the system is 10.14 – 10.15, meaning the notification is silently dropped.
Adding UNNotificationPresentationOptionAlert (and optionally Sound) for these versions preserves user-visible behaviour.

-    UNNotificationPresentationOptions options = 0;
+    UNNotificationPresentationOptions options = UNNotificationPresentationOptionAlert |
+                                                UNNotificationPresentationOptionSound;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2ed2cd5 and e7cbd46.

📒 Files selected for processing (1)
  • v2/internal/frontend/desktop/darwin/WailsContext.m (4 hunks)
🔇 Additional comments (1)
v2/internal/frontend/desktop/darwin/WailsContext.m (1)

40-47: Guard usages of UN* APIs with the same compile-time check used for the import

UNUserNotificationCenter, UNNotification*, etc. are referenced throughout the file without #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 guards.
When building on an SDK < 10.14 this will still fail at compile-time even though the runtime @available checks are present.

Consider wrapping the entire notification section (declarations, protocol conformance, and method bodies) in the same macro guard so the file compiles on older SDKs.

Would you run an older macOS SDK build (or CI job) to prove the compilation path is still green?

@popaprozac
Copy link
Author

Example app.go:

package main

import (
	"context"
	"fmt"

	"github.com/wailsapp/wails/v2/pkg/runtime"
)

// App struct
type App struct {
	ctx context.Context
}

// NewApp creates a new App application struct
func NewApp() *App {
	return &App{}
}

// startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
	a.ctx = ctx

	var err error
	err = runtime.InitializeNotifications(ctx)
	if err != nil {
		fmt.Println("Error initializing notifications:", err)
		return
	}

	runtime.OnNotificationResponse(ctx, func(result runtime.NotificationResult) {
		fmt.Printf("Notification response: %v\n", result)
		runtime.EventsEmit(ctx, "notification:response", result)
	})

	authorized, err := runtime.RequestNotificationAuthorization(ctx)
	if err != nil {
		fmt.Println("Error requesting notification authorization:", err)
		return
	}
	if authorized {
		fmt.Println("Notification authorization granted")
	} else {
		fmt.Println("Notification authorization denied")
	}

	runtime.SendNotification(ctx, runtime.NotificationOptions{
		ID:       "notification-id",
		Title:    "Notification Title",
		Subtitle: "Notification Subtitle",
		Body:     "Notification Body",
		Data: map[string]interface{}{
			"user-id": "12345",
		},
	})
}

// Greet returns a greeting for the given name
func (a *App) Greet(name string) string {
	return fmt.Sprintf("Hello %s, It's show time!", name)
}

Listen to the event on the frontend and 👌

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
v2/internal/frontend/desktop/windows/winc/icon.go (1)

197-211: Optimize the pixel conversion loop.

The current pixel conversion code is correct but could be optimized by directly accessing the RGBA image's pixel data through its Pix field rather than using Set() for each pixel.

	// Convert DIB to RGBA
	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			// DIB is bottom-up, so we need to invert Y
			dibIndex := ((height-1-y)*width + x) * 4
+			// RGBA image is top-down
+			imgIndex := (y*width + x) * 4

			// BGRA to RGBA
-			b := bits[dibIndex]
-			g := bits[dibIndex+1]
-			r := bits[dibIndex+2]
-			a := bits[dibIndex+3]
-
-			// Set pixel in the image
-			img.Set(x, y, color.RGBA{R: r, G: g, B: b, A: a})
+			img.Pix[imgIndex] = bits[dibIndex+2]     // R
+			img.Pix[imgIndex+1] = bits[dibIndex+1]   // G
+			img.Pix[imgIndex+2] = bits[dibIndex]     // B
+			img.Pix[imgIndex+3] = bits[dibIndex+3]   // A
		}
	}

This approach is significantly faster as it avoids the overhead of the Set() method call and color struct creation for each pixel.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e7cbd46 and ebfb139.

📒 Files selected for processing (3)
  • v2/internal/frontend/desktop/linux/notifications.go (1 hunks)
  • v2/internal/frontend/desktop/windows/notifications.go (1 hunks)
  • v2/internal/frontend/desktop/windows/winc/icon.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • v2/internal/frontend/desktop/linux/notifications.go
  • v2/internal/frontend/desktop/windows/notifications.go
🔇 Additional comments (1)
v2/internal/frontend/desktop/windows/winc/icon.go (1)

213-222: LGTM: File handling is well implemented

The file creation, error handling, and PNG encoding are implemented correctly with proper resource cleanup.

Comment on lines 155 to 163
hdc, _, _ := createCompatibleDC.Call(0)
if hdc == 0 {
return syscall.EINVAL
}
defer deleteDC.Call(hdc)

// Select bitmap into DC
oldBitmap, _, _ := selectObject.Call(hdc, uintptr(iconInfo.HbmColor))
defer selectObject.Call(hdc, oldBitmap)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve error handling and use module-level procedures.

Update the DC creation and bitmap selection code to use module-level procedures and improve error handling by checking return values.

	// Create DC
-	hdc, _, _ := createCompatibleDC.Call(0)
+	hdc, _, err := procCreateCompatibleDC.Call(0)
	if hdc == 0 {
-		return syscall.EINVAL
+		return fmt.Errorf("failed to create compatible DC: %w", err)
	}
-	defer deleteDC.Call(hdc)
+	defer procDeleteDC.Call(hdc)

	// Select bitmap into DC
-	oldBitmap, _, _ := selectObject.Call(hdc, uintptr(iconInfo.HbmColor))
+	oldBitmap, _, err := procSelectObject.Call(hdc, uintptr(iconInfo.HbmColor))
+	if oldBitmap == 0 {
+		return fmt.Errorf("failed to select bitmap: %w", err)
+	}
-	defer selectObject.Call(hdc, oldBitmap)
+	defer procSelectObject.Call(hdc, oldBitmap)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
v2/internal/frontend/desktop/darwin/WailsContext.m (1)

844-847: ⚠️ Potential issue

Add null safety checks for required fields

The title and body parameters are required for notification content, but stringWithUTF8String: will crash if passed NULL pointers. While subtitle has a null check, these other required fields don't.

Add defensive checks to prevent crashes:

-    NSString *nsTitle = [NSString stringWithUTF8String:title];
-    NSString *nsSubtitle = subtitle ? [NSString stringWithUTF8String:subtitle] : @"";
-    NSString *nsBody = [NSString stringWithUTF8String:body];
+    if (title == NULL) title = "";
+    if (body == NULL) body = "";
+    
+    NSString *nsTitle = [NSString stringWithUTF8String:title];
+    NSString *nsSubtitle = subtitle ? [NSString stringWithUTF8String:subtitle] : @"";
+    NSString *nsBody = [NSString stringWithUTF8String:body];
🧹 Nitpick comments (1)
v2/internal/frontend/desktop/darwin/WailsContext.m (1)

938-1014: Consider batch handling for category operations

The category registration performs multiple operations (retrieving categories, modifying the set, setting categories) that could cause race conditions if multiple registration requests occur simultaneously.

Consider using a serialized queue or a more atomic approach when modifying notification categories to prevent potential race conditions.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ebfb139 and 31a6d03.

📒 Files selected for processing (2)
  • v2/internal/frontend/desktop/darwin/WailsContext.m (4 hunks)
  • v2/internal/frontend/desktop/windows/winc/icon.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • v2/internal/frontend/desktop/windows/winc/icon.go
🔇 Additional comments (6)
v2/internal/frontend/desktop/darwin/WailsContext.m (6)

849-849: Good fix: Memory leak resolved with autorelease

The implementation correctly uses autorelease to prevent memory leaks in a manual reference counting environment.


793-800: Good fix: Delegate initialization improved

The implementation correctly sets the notification center delegate directly rather than using dispatch_once, avoiding potential dangling pointer issues if the context is deallocated and recreated.


737-750: Good implementation of notification presentation options

The code correctly handles different presentation options based on macOS version, using appropriate availability checks for macOS 11.0+ specific features.


752-791: Well-implemented notification response handling

The notification response handling is thorough and includes proper error handling for JSON serialization, along with comprehensive payload construction that captures all relevant notification data.


802-824: Thorough authorization implementation with proper error handling

The notification authorization request implementation includes appropriate version checks, delegate verification, and comprehensive error handling in the completion handler.


721-1063: Overall well-implemented notification system

The notification implementation provides a comprehensive cross-platform bridge for macOS notifications with proper availability checks, error handling, and memory management throughout.

@leaanthony
Copy link
Member

@popaprozac - Could we hardcode the path for this command? cmd := exec.Command("codesign", "--force", "--deep", "--sign", "-", options.CompiledBinary)?

@popaprozac
Copy link
Author

@popaprozac - Could we hardcode the path for this command? cmd := exec.Command("codesign", "--force", "--deep", "--sign", "-", options.CompiledBinary)?

/usr/bin/codesign? Sure and good call

@sonarqubecloud
Copy link

sonarqubecloud bot commented May 1, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
3.9% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@popaprozac
Copy link
Author

popaprozac commented Jul 19, 2025

@leaanthony I fear our window for this came and went. Was so looking forward to closing #1788 😅. Should I go ahead and close?

@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Jul 19, 2025
@popaprozac
Copy link
Author

I was looking back at this and found a bug on Windows where if you don't pass arbitrary Data to a basic notification the response is not parsed properly.
I will update that and if we think this still has legs I will update changelogs, add docs, etc 😄

@popaprozac
Copy link
Author

@leaanthony fixed en/decoding issue for this PR and opened #4450 for v3

@popaprozac popaprozac changed the title [V2] Notifications API [v2] Notifications API Jul 28, 2025
@coderabbitai coderabbitai bot mentioned this pull request Jul 28, 2025
15 tasks
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
v2/internal/frontend/desktop/darwin/WailsContext.m (4)

835-851: Auth status handling OK; consider provisional status (future-proofing)

Optional: treat provisional/ephemeral (if ever exposed on macOS SDKs) as authorized. Can be deferred.


882-912: Default/fallback for identifier (optional hardening)

If identifier is ever NULL, build a UUID to avoid a crash or invalid request.

Apply this diff:

-    NSString *nsIdentifier = [NSString stringWithUTF8String:identifier];
+    NSString *nsIdentifier = identifier
+        ? [NSString stringWithUTF8String:identifier]
+        : [NSUUID UUID].UUIDString;

913-945: Same identifier hardening and error path LGTM

Mirror the identifier NULL-guard here as well; otherwise looks solid.


947-1023: Support foreground actions (optional)

Allow actions JSON to specify "foreground": true to bring the app to front when tapped.

Apply this diff:

-        BOOL destructive = [actionDict[@"destructive"] boolValue];
+        BOOL destructive = [actionDict[@"destructive"] boolValue];
+        BOOL foreground  = [actionDict[@"foreground"] boolValue];
@@
-            if (destructive) options |= UNNotificationActionOptionDestructive;
+            if (destructive) options |= UNNotificationActionOptionDestructive;
+            if (foreground)  options |= UNNotificationActionOptionForeground;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2c189b and 6e81c5b.

⛔ Files ignored due to path filters (1)
  • v2/go.sum is excluded by !**/*.sum
📒 Files selected for processing (2)
  • v2/go.mod (1 hunks)
  • v2/internal/frontend/desktop/darwin/WailsContext.m (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • v2/go.mod
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: popaprozac
PR: wailsapp/wails#4098
File: v3/pkg/services/notifications/notifications_windows.go:0-0
Timestamp: 2025-03-24T06:54:22.127Z
Learning: popaprozac prefers to focus on getting the Notifications API functionality working first and may consider code cleanup/refactoring for payload encoding logic in notifications_windows.go at a later time.
Learnt from: popaprozac
PR: wailsapp/wails#4098
File: v3/pkg/services/notifications/notifications.go:46-55
Timestamp: 2025-03-24T20:22:56.233Z
Learning: In the notifications package, initialization of the `Service` struct is handled through platform-specific `New()` functions in each implementation file (darwin, windows, linux) rather than a generic constructor in the main package file. Each platform implementation follows a singleton pattern using `notificationServiceOnce.Do()` and creates a global `NotificationService` variable that's accessed through a thread-safe `getNotificationService()` function.
Learnt from: popaprozac
PR: wailsapp/wails#4098
File: v3/pkg/services/notifications/notifications.go:46-55
Timestamp: 2025-03-24T20:22:56.233Z
Learning: In the notifications package, initialization of the `Service` struct is handled through platform-specific `New()` functions in each implementation file (darwin, windows, linux) rather than a generic constructor in the main package file.
📚 Learning: 2025-03-23T00:41:39.612Z
Learnt from: popaprozac
PR: wailsapp/wails#4098
File: v3/pkg/services/notifications/notifications_darwin.go:39-46
Timestamp: 2025-03-23T00:41:39.612Z
Learning: For the macOS notifications implementation in Wails, an early panic is used when the bundle identifier check fails rather than returning an error, because the Objective-C code would crash later anyway. The panic provides clear instructions to developers about bundling and signing requirements.

Applied to files:

  • v2/internal/frontend/desktop/darwin/WailsContext.m
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (10)
v2/internal/frontend/desktop/darwin/WailsContext.m (10)

8-8: Include looks correct

No issues with the new include. Matches the added notification surface.


40-47: Confirm C-string lifetime in extern callbacks

UTF8String returns a pointer only valid while the backing NSString is alive. Please confirm that captureResult and didReceiveNotificationResponse synchronously copy their string arguments. This file elsewhere releases temporary NSStrings immediately after the callback, implying that contract.


729-736: Availability check LGTM

Clear, simple guard for macOS 10.14+.


738-745: Bundle identifier check is fine; ensure it’s enforced upstream

Returning NO is OK here. Please ensure the Go layer calls this early and fails fast (as v3 does), otherwise the Obj-C layer may crash later when scheduling notifications.


802-809: Delegate setup LGTM

Assigning the UNUserNotificationCenter delegate each time is safer than a one-time capture and avoids dangling references.


811-833: Authorization request flow LGTM

Happy path and error propagation via captureResult looks correct.


1025-1051: Category removal flow LGTM

Correctly reconciles the category set and reports result.


1052-1072: Removal APIs LGTM

Direct calls to remove pending/delivered notifications look correct.


1083-1101: About dialog change is cosmetic

No functional concerns.


853-880: Nil-safety for required fields (title, body)

stringWithUTF8String: can crash if passed a NULL pointer. Guard title and body similarly to subtitle.

Apply this diff:

-    NSString *nsTitle = [NSString stringWithUTF8String:title];
-    NSString *nsSubtitle = subtitle ? [NSString stringWithUTF8String:subtitle] : @"";
-    NSString *nsBody = [NSString stringWithUTF8String:body];
+    if (title == NULL)   title = "";
+    if (body  == NULL)   body  = "";
+    if (subtitle == NULL) subtitle = "";
+
+    NSString *nsTitle    = [NSString stringWithUTF8String:title]    ?: @"";
+    NSString *nsSubtitle = [NSString stringWithUTF8String:subtitle] ?: @"";
+    NSString *nsBody     = [NSString stringWithUTF8String:body]     ?: @"";

Comment on lines +746 to +760
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler API_AVAILABLE(macos(10.14)) {
UNNotificationPresentationOptions options = 0;

if (@available(macOS 11.0, *)) {
// These options are only available in macOS 11.0+
options = UNNotificationPresentationOptionList |
UNNotificationPresentationOptionBanner |
UNNotificationPresentationOptionSound;
}

completionHandler(options);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Foreground notifications are suppressed on 10.14–10.15 — add fallback

When the app is in the foreground, pre–Big Sur should use Alert | Sound. Returning 0 hides the notification entirely.

Apply this diff:

-    UNNotificationPresentationOptions options = 0;
-    
-    if (@available(macOS 11.0, *)) {
+    UNNotificationPresentationOptions options;
+    if (@available(macOS 11.0, *)) {
         // These options are only available in macOS 11.0+
         options = UNNotificationPresentationOptionList | 
                   UNNotificationPresentationOptionBanner | 
                   UNNotificationPresentationOptionSound;
-    }
-    
+    } else {
+        options = UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionSound;
+    }
+
     completionHandler(options);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler API_AVAILABLE(macos(10.14)) {
UNNotificationPresentationOptions options = 0;
if (@available(macOS 11.0, *)) {
// These options are only available in macOS 11.0+
options = UNNotificationPresentationOptionList |
UNNotificationPresentationOptionBanner |
UNNotificationPresentationOptionSound;
}
completionHandler(options);
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler API_AVAILABLE(macos(10.14)) {
UNNotificationPresentationOptions options;
if (@available(macOS 11.0, *)) {
// These options are only available in macOS 11.0+
options = UNNotificationPresentationOptionList |
UNNotificationPresentationOptionBanner |
UNNotificationPresentationOptionSound;
} else {
options = UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionSound;
}
completionHandler(options);
}
🤖 Prompt for AI Agents
In v2/internal/frontend/desktop/darwin/WailsContext.m around lines 746 to 760,
the willPresentNotification handler currently sets
UNNotificationPresentationOptions to 0 for pre-Big Sur macOS (10.14–10.15) which
suppresses foreground notifications; change the logic so that if
@available(macOS 11.0, *) you set List | Banner | Sound as before, otherwise set
UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionSound
(the Alert|Sound fallback) before calling completionHandler(options); ensure you
reference the correct enum constants for older macOS SDKs and keep the
availability checks intact.

Comment on lines +761 to +801
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(macos(10.14)) {

NSMutableDictionary *payload = [NSMutableDictionary dictionary];

[payload setObject:response.notification.request.identifier forKey:@"id"];
[payload setObject:response.actionIdentifier forKey:@"actionIdentifier"];
[payload setObject:response.notification.request.content.title ?: @"" forKey:@"title"];
[payload setObject:response.notification.request.content.body ?: @"" forKey:@"body"];

if (response.notification.request.content.categoryIdentifier) {
[payload setObject:response.notification.request.content.categoryIdentifier forKey:@"categoryIdentifier"];
}

if (response.notification.request.content.subtitle) {
[payload setObject:response.notification.request.content.subtitle forKey:@"subtitle"];
}

if (response.notification.request.content.userInfo) {
[payload setObject:response.notification.request.content.userInfo forKey:@"userInfo"];
}

if ([response isKindOfClass:[UNTextInputNotificationResponse class]]) {
UNTextInputNotificationResponse *textResponse = (UNTextInputNotificationResponse *)response;
[payload setObject:textResponse.userText forKey:@"userText"];
}

NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:payload options:0 error:&error];
if (error) {
NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]];
didReceiveNotificationResponse(NULL, [errorMsg UTF8String]);
} else {
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
didReceiveNotificationResponse([jsonString UTF8String], NULL);
}

completionHandler();
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Memory leak: jsonString is never released (MRC build)

[[NSString alloc] initWithData:...] returns a retained object. Autorelease it (consistent with the rest of the file) to avoid leaking on every response.

Apply this diff:

-        NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
+        NSString *jsonString = [[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] autorelease];
         didReceiveNotificationResponse([jsonString UTF8String], NULL);
🤖 Prompt for AI Agents
In v2/internal/frontend/desktop/darwin/WailsContext.m around lines 761 to 801,
the NSString *jsonString created with [[NSString alloc] initWithData:encoding:]
is retained and never released under MRC; change its creation to return an
autoreleased object (consistent with the rest of the file) by calling
autorelease on the allocated string (or alternatively release it after calling
didReceiveNotificationResponse) so the string is not leaked.

Copy link
Member

@leaanthony leaanthony left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing stuff @popaprozac! Could you please update the docs with the new options/settings please? 🙏 Oh and also update the Changelog :-)

@popaprozac
Copy link
Author

Hello! Of course. I've been away from this for a bit but will get to it this week!

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
3.9% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e81c5b and 5231199.

📒 Files selected for processing (3)
  • website/docs/guides/notifications.mdx (1 hunks)
  • website/docs/reference/runtime/notification.mdx (1 hunks)
  • website/src/pages/changelog.mdx (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: popaprozac
PR: wailsapp/wails#4098
File: v3/pkg/services/notifications/notifications_windows.go:0-0
Timestamp: 2025-03-24T06:54:22.127Z
Learning: popaprozac prefers to focus on getting the Notifications API functionality working first and may consider code cleanup/refactoring for payload encoding logic in notifications_windows.go at a later time.
Learnt from: popaprozac
PR: wailsapp/wails#4098
File: v3/pkg/services/notifications/notifications.go:46-55
Timestamp: 2025-03-24T20:22:56.233Z
Learning: In the notifications package, initialization of the `Service` struct is handled through platform-specific `New()` functions in each implementation file (darwin, windows, linux) rather than a generic constructor in the main package file.
Learnt from: popaprozac
PR: wailsapp/wails#4098
File: v3/pkg/services/notifications/notifications.go:46-55
Timestamp: 2025-03-24T20:22:56.233Z
Learning: In the notifications package, initialization of the `Service` struct is handled through platform-specific `New()` functions in each implementation file (darwin, windows, linux) rather than a generic constructor in the main package file. Each platform implementation follows a singleton pattern using `notificationServiceOnce.Do()` and creates a global `NotificationService` variable that's accessed through a thread-safe `getNotificationService()` function.
📚 Learning: 2025-03-24T20:22:56.233Z
Learnt from: popaprozac
PR: wailsapp/wails#4098
File: v3/pkg/services/notifications/notifications.go:46-55
Timestamp: 2025-03-24T20:22:56.233Z
Learning: In the notifications package, initialization of the `Service` struct is handled through platform-specific `New()` functions in each implementation file (darwin, windows, linux) rather than a generic constructor in the main package file. Each platform implementation follows a singleton pattern using `notificationServiceOnce.Do()` and creates a global `NotificationService` variable that's accessed through a thread-safe `getNotificationService()` function.

Applied to files:

  • website/docs/reference/runtime/notification.mdx
  • website/docs/guides/notifications.mdx
📚 Learning: 2025-03-24T20:22:56.233Z
Learnt from: popaprozac
PR: wailsapp/wails#4098
File: v3/pkg/services/notifications/notifications.go:46-55
Timestamp: 2025-03-24T20:22:56.233Z
Learning: In the notifications package, initialization of the `Service` struct is handled through platform-specific `New()` functions in each implementation file (darwin, windows, linux) rather than a generic constructor in the main package file.

Applied to files:

  • website/docs/guides/notifications.mdx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: semgrep-cloud-platform/scan

@popaprozac
Copy link
Author

@leaanthony I'm having some issues as I go through this. When I checkout this branch and use it locally I am running into some build constraint issues

package github.com/wailsapp/wails/v2
	imports github.com/wailsapp/wails/v2/pkg/application
	imports github.com/wailsapp/wails/v2/internal/app
	imports github.com/wailsapp/wails/v2/internal/frontend
	imports github.com/wailsapp/wails/v2/pkg/options
	imports github.com/wailsapp/wails/v2/pkg/options/windows
	imports golang.org/x/sys/windows: build constraints exclude all Go files in /Users/<username>/go/pkg/mod/golang.org/x/[email protected]/windows

I can't for the life of me understand where the issue is but will update us when this is ready again for review.

Had Claude help with the docs, will need to clean up its mistakes. Thanks 🐰

@popaprozac
Copy link
Author

Looks like some recent changes to v2/pkg/options/windows/windows.go are causing the issue? It imports this

import (
	"golang.org/x/sys/windows"
)

and then in v2/pkg/options/options.go we import v2/pkg/options/windows/windows.go

Am I missing something?

@popaprozac
Copy link
Author

@leaanthony thoughts?

@sonarqubecloud
Copy link

sonarqubecloud bot commented Nov 2, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
3.9% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@leaanthony
Copy link
Member

I've got a fix for the build issue locally. Will see if I can push it tonight. If you see this before then, the fix is simply swapping the const references with const data e.g. 0x800...

@popaprozac
Copy link
Author

@leaanthony I'll take a look at this asap, thanks. Need to clean up documentation too...

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
v2/internal/frontend/desktop/darwin/notifications.go (2)

65-67: Remove unused interface.

The ChannelHandler interface is defined but never used. The GetChannel method is implemented directly on *Frontend without implementing this interface.

🔎 Proposed removal
-type ChannelHandler interface {
-	GetChannel(id int) (chan notificationChannel, bool)
-}
-

390-398: Consider adding panic recovery to callback goroutine.

The callback is invoked in a goroutine without panic recovery. If user-provided callback code panics, it will crash the application.

🔎 Proposed addition of panic recovery
 func handleNotificationResult(result frontend.NotificationResult) {
 	callbackLock.Lock()
 	callback := notificationResultCallback
 	callbackLock.Unlock()
 
 	if callback != nil {
-		go callback(result)
+		go func() {
+			defer func() {
+				if r := recover(); r != nil {
+					// Log panic but don't crash the app
+					fmt.Fprintf(os.Stderr, "panic in notification callback: %v\n", r)
+				}
+			}()
+			callback(result)
+		}()
 	}
 }

Note: You'll need to import os if you add logging.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be320ba and 426dcb8.

📒 Files selected for processing (1)
  • v2/internal/frontend/desktop/darwin/notifications.go
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications_windows.go:0-0
Timestamp: 2025-03-24T06:54:22.127Z
Learning: popaprozac prefers to focus on getting the Notifications API functionality working first and may consider code cleanup/refactoring for payload encoding logic in notifications_windows.go at a later time.
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications_darwin.go:39-46
Timestamp: 2025-03-23T00:41:39.612Z
Learning: For the macOS notifications implementation in Wails, an early panic is used when the bundle identifier check fails rather than returning an error, because the Objective-C code would crash later anyway. The panic provides clear instructions to developers about bundling and signing requirements.
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications_windows.go:91-113
Timestamp: 2025-02-24T06:08:55.645Z
Learning: The JSON quote handling in Windows notifications' activation arguments (v3/pkg/services/notifications/notifications_windows.go) has a known limitation with single-quote collisions that needs to be addressed after initial testing.
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications.go:46-55
Timestamp: 2025-03-24T20:22:56.233Z
Learning: In the notifications package, initialization of the `Service` struct is handled through platform-specific `New()` functions in each implementation file (darwin, windows, linux) rather than a generic constructor in the main package file.
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications.go:46-55
Timestamp: 2025-03-24T20:22:56.233Z
Learning: In the notifications package, initialization of the `Service` struct is handled through platform-specific `New()` functions in each implementation file (darwin, windows, linux) rather than a generic constructor in the main package file. Each platform implementation follows a singleton pattern using `notificationServiceOnce.Do()` and creates a global `NotificationService` variable that's accessed through a thread-safe `getNotificationService()` function.
📚 Learning: 2025-03-24T20:22:56.233Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications.go:46-55
Timestamp: 2025-03-24T20:22:56.233Z
Learning: In the notifications package, initialization of the `Service` struct is handled through platform-specific `New()` functions in each implementation file (darwin, windows, linux) rather than a generic constructor in the main package file.

Applied to files:

  • v2/internal/frontend/desktop/darwin/notifications.go
📚 Learning: 2025-03-24T20:22:56.233Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications.go:46-55
Timestamp: 2025-03-24T20:22:56.233Z
Learning: In the notifications package, initialization of the `Service` struct is handled through platform-specific `New()` functions in each implementation file (darwin, windows, linux) rather than a generic constructor in the main package file. Each platform implementation follows a singleton pattern using `notificationServiceOnce.Do()` and creates a global `NotificationService` variable that's accessed through a thread-safe `getNotificationService()` function.

Applied to files:

  • v2/internal/frontend/desktop/darwin/notifications.go
📚 Learning: 2025-03-23T00:41:39.612Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications_darwin.go:39-46
Timestamp: 2025-03-23T00:41:39.612Z
Learning: For the macOS notifications implementation in Wails, an early panic is used when the bundle identifier check fails rather than returning an error, because the Objective-C code would crash later anyway. The panic provides clear instructions to developers about bundling and signing requirements.

Applied to files:

  • v2/internal/frontend/desktop/darwin/notifications.go
📚 Learning: 2025-04-29T23:54:07.488Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4256
File: v2/internal/frontend/desktop/linux/notifications.go:27-28
Timestamp: 2025-04-29T23:54:07.488Z
Learning: In Wails v2, unlike v3-alpha which has a `ServiceShutdown` method for services, there is no standardized teardown pattern for frontend implementations. When implementing features that require cleanup (like goroutines or resources), add explicit cleanup methods (e.g., `CleanupNotifications()`) that handle resource release, context cancellation, and connection closure.

Applied to files:

  • v2/internal/frontend/desktop/darwin/notifications.go
🧬 Code graph analysis (1)
v2/internal/frontend/desktop/darwin/notifications.go (4)
v2/internal/frontend/frontend.go (5)
  • Frontend (124-203)
  • NotificationResult (119-122)
  • NotificationOptions (80-87)
  • NotificationCategory (97-103)
  • NotificationResponse (106-115)
v2/pkg/runtime/notifications.go (18)
  • NotificationResult (23-23)
  • InitializeNotifications (25-28)
  • IsNotificationAvailable (30-33)
  • RequestNotificationAuthorization (35-38)
  • CheckNotificationAuthorization (40-43)
  • SendNotification (45-48)
  • NotificationOptions (10-10)
  • SendNotificationWithActions (50-53)
  • RegisterNotificationCategory (55-58)
  • NotificationCategory (16-16)
  • RemoveNotificationCategory (60-63)
  • RemoveAllPendingNotifications (65-68)
  • RemovePendingNotification (70-73)
  • RemoveAllDeliveredNotifications (75-78)
  • RemoveDeliveredNotification (80-83)
  • RemoveNotification (85-88)
  • OnNotificationResponse (90-93)
  • NotificationResponse (19-19)
v2/internal/frontend/desktop/windows/notifications.go (1)
  • DefaultActionIdentifier (38-38)
v2/internal/frontend/desktop/linux/notifications.go (1)
  • DefaultActionIdentifier (44-44)
🔇 Additional comments (13)
v2/internal/frontend/desktop/darwin/notifications.go (13)

1-27: LGTM: Build setup and imports are correct.

The build constraints, cgo configuration, and framework linking are properly configured for macOS notifications. The conditional linking of UserNotifications for macOS 11.0+ is appropriate.


29-58: LGTM: Thread-safe state management.

The package-level state is properly protected with appropriate mutexes. The use of sync.RWMutex for currentFrontend is well-suited for the read-heavy access pattern.


69-94: LGTM: Proper initialization sequence.

The initialization validates system availability and bundle identifier before setting up the notification infrastructure. Error handling is appropriate for the v2 API surface.


96-128: LGTM: Authorization flow is well-structured.

The authorization methods properly use timeouts and channels for async operations. The timeout durations (3 minutes for request, 15 seconds for check) are reasonable.


131-216: LGTM: Notification sending methods are correct.

Both SendNotification and SendNotificationWithActions properly manage C memory with deferred cleanup, handle JSON marshaling errors, and use appropriate timeouts for async operations.


220-285: LGTM: Category management is properly implemented.

The category registration correctly handles optional reply fields with conditional C string allocation, and JSON marshaling for actions is properly error-checked.


315-324: LGTM: Platform-specific stub is well-documented.

The stub correctly maintains cross-platform interface compatibility. The comment clearly directs users to platform-specific alternatives.


326-330: LGTM: Callback registration is thread-safe.


332-353: LGTM: C callback bridge is correctly implemented.

The exported function safely handles nil frontend and missing channels. The channel cleanup (via GetChannel) prevents double-delivery.


355-388: LGTM: Response parsing and error handling are thorough.

The notification response handler properly validates the payload, unmarshals JSON, and normalizes Apple's default action identifier for cross-platform consistency. Error handling covers all failure modes.


402-419: LGTM: Channel registration is thread-safe.

The defensive re-initialization of the channels map (lines 407-410) provides safety if methods are called before InitializeNotifications. The buffered channel (size 1) prevents goroutine leaks.


421-448: LGTM: Channel cleanup is safe.

GetChannel correctly deletes the channel before returning it, preventing double-delivery. cleanupChannel safely handles nil map and missing channels.


287-313: These removal methods correctly implement the underlying macOS API.

The unconditional nil return is appropriate because the underlying Objective-C functions do not return errors, and internal OS-level failures are ignored. This pattern matches the v3 design where removal operations are fire-and-forget. No changes needed.

@popaprozac
Copy link
Author

@leaanthony I've been super busy but think I got this back where it needs to be. Lmk if I am missing anything critical

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
website/docs/reference/runtime/notification.mdx (1)

165-167: Linux reply-field support incorrectly marked as supported.

The table shows Linux supports HasReplyField, ReplyPlaceholder, and ReplyButtonTitle (✅), but line 277 explicitly states "User text input not supported" for Linux. The NotificationResponse table (line 220) correctly shows Linux does NOT support UserText. Remove the ✅ marks in the Linux column for these three reply-related fields to match the documented limitations.

🔎 Proposed fix
 | Field            | Description                                    | Win | Mac | Lin |
 |------------------|------------------------------------------------|-----|-----|-----|
 | ID               | Unique identifier for the category            | ✅   | ✅   | ✅   |
 | Actions          | Array of action buttons                       | ✅   | ✅   | ✅   |
-| HasReplyField    | Whether to include a text input field          | ✅   | ✅   | ✅   |
-| ReplyPlaceholder | Placeholder text for the input field          | ✅   | ✅   | ✅   |
-| ReplyButtonTitle | Text for the reply button                     | ✅   | ✅   | ✅   |
+| HasReplyField    | Whether to include a text input field          | ✅   | ✅   |     |
+| ReplyPlaceholder | Placeholder text for the input field          | ✅   | ✅   |     |
+| ReplyButtonTitle | Text for the reply button                     | ✅   | ✅   |     |
🧹 Nitpick comments (1)
website/docs/reference/runtime/notification.mdx (1)

17-17: Consider adding subject for grammatical completeness.

The phrase "Should be called during app startup" is grammatically incomplete. Consider changing it to "It should be called during app startup" for better readability.

🔎 Proposed fix
-Initializes the notification system. Should be called during app startup.
+Initializes the notification system. It should be called during app startup.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 426dcb8 and 2783f55.

📒 Files selected for processing (2)
  • website/docs/guides/notifications.mdx
  • website/docs/reference/runtime/notification.mdx
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications_windows.go:0-0
Timestamp: 2025-03-24T06:54:22.127Z
Learning: popaprozac prefers to focus on getting the Notifications API functionality working first and may consider code cleanup/refactoring for payload encoding logic in notifications_windows.go at a later time.
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications_darwin.go:39-46
Timestamp: 2025-03-23T00:41:39.612Z
Learning: For the macOS notifications implementation in Wails, an early panic is used when the bundle identifier check fails rather than returning an error, because the Objective-C code would crash later anyway. The panic provides clear instructions to developers about bundling and signing requirements.
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications_windows.go:91-113
Timestamp: 2025-02-24T06:08:55.645Z
Learning: The JSON quote handling in Windows notifications' activation arguments (v3/pkg/services/notifications/notifications_windows.go) has a known limitation with single-quote collisions that needs to be addressed after initial testing.
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications.go:46-55
Timestamp: 2025-03-24T20:22:56.233Z
Learning: In the notifications package, initialization of the `Service` struct is handled through platform-specific `New()` functions in each implementation file (darwin, windows, linux) rather than a generic constructor in the main package file.
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications.go:46-55
Timestamp: 2025-03-24T20:22:56.233Z
Learning: In the notifications package, initialization of the `Service` struct is handled through platform-specific `New()` functions in each implementation file (darwin, windows, linux) rather than a generic constructor in the main package file. Each platform implementation follows a singleton pattern using `notificationServiceOnce.Do()` and creates a global `NotificationService` variable that's accessed through a thread-safe `getNotificationService()` function.
📚 Learning: 2025-03-24T20:22:56.233Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications.go:46-55
Timestamp: 2025-03-24T20:22:56.233Z
Learning: In the notifications package, initialization of the `Service` struct is handled through platform-specific `New()` functions in each implementation file (darwin, windows, linux) rather than a generic constructor in the main package file. Each platform implementation follows a singleton pattern using `notificationServiceOnce.Do()` and creates a global `NotificationService` variable that's accessed through a thread-safe `getNotificationService()` function.

Applied to files:

  • website/docs/guides/notifications.mdx
  • website/docs/reference/runtime/notification.mdx
📚 Learning: 2025-03-24T20:22:56.233Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications.go:46-55
Timestamp: 2025-03-24T20:22:56.233Z
Learning: In the notifications package, initialization of the `Service` struct is handled through platform-specific `New()` functions in each implementation file (darwin, windows, linux) rather than a generic constructor in the main package file.

Applied to files:

  • website/docs/guides/notifications.mdx
  • website/docs/reference/runtime/notification.mdx
📚 Learning: 2025-04-29T23:54:07.488Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4256
File: v2/internal/frontend/desktop/linux/notifications.go:27-28
Timestamp: 2025-04-29T23:54:07.488Z
Learning: In Wails v2, unlike v3-alpha which has a `ServiceShutdown` method for services, there is no standardized teardown pattern for frontend implementations. When implementing features that require cleanup (like goroutines or resources), add explicit cleanup methods (e.g., `CleanupNotifications()`) that handle resource release, context cancellation, and connection closure.

Applied to files:

  • website/docs/guides/notifications.mdx
📚 Learning: 2025-03-23T00:41:39.612Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications_darwin.go:39-46
Timestamp: 2025-03-23T00:41:39.612Z
Learning: For the macOS notifications implementation in Wails, an early panic is used when the bundle identifier check fails rather than returning an error, because the Objective-C code would crash later anyway. The panic provides clear instructions to developers about bundling and signing requirements.

Applied to files:

  • website/docs/guides/notifications.mdx
📚 Learning: 2025-03-24T06:54:22.127Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications_windows.go:0-0
Timestamp: 2025-03-24T06:54:22.127Z
Learning: popaprozac prefers to focus on getting the Notifications API functionality working first and may consider code cleanup/refactoring for payload encoding logic in notifications_windows.go at a later time.

Applied to files:

  • website/docs/guides/notifications.mdx
📚 Learning: 2025-02-24T06:23:10.967Z
Learnt from: popaprozac
Repo: wailsapp/wails PR: 4098
File: v3/pkg/services/notifications/notifications_windows.go:113-164
Timestamp: 2025-02-24T06:23:10.967Z
Learning: In Windows Toast notifications (notifications_windows.go), metadata must be appended to each action's Arguments because the toast activated callback only returns the Arguments string of the clicked action. This is achieved by appending the same escaped JSON data (with single quotes) to all actions using the ":::" delimiter.

Applied to files:

  • website/docs/reference/runtime/notification.mdx
🪛 LanguageTool
website/docs/reference/runtime/notification.mdx

[style] ~17-~17: To form a complete sentence, be sure to include a subject.
Context: ...s Initializes the notification system. Should be called during app startup. Go: `Ini...

(MISSING_IT_THERE)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Run Go Tests (macos-latest, 1.23)
  • GitHub Check: Run Go Tests (ubuntu-24.04, 1.23)
  • GitHub Check: Run Go Tests (ubuntu-22.04, 1.23)
  • GitHub Check: Run Go Tests (windows-latest, 1.23)
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (2)
website/docs/guides/notifications.mdx (1)

1-233: Excellent comprehensive guide for the Notifications API.

This documentation provides a thorough, well-structured guide covering all aspects of the cross-platform notifications system. The code examples demonstrate proper error handling, the platform considerations are accurate and consistent, and the best practices section provides valuable guidance. The progressive structure from basic to advanced usage makes it easy to follow.

website/docs/reference/runtime/notification.mdx (1)

1-319: API reference documentation is comprehensive and well-structured.

The documentation thoroughly covers all runtime functions, types, platform-specific behavior, and action identifiers with clear Go signatures and descriptions. The examples demonstrate proper usage patterns, and the platform considerations help developers understand cross-platform differences. After addressing the Linux reply-field inconsistency, this will be an excellent reference.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
3.9% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants