tauri-apps / tauri-apps/plugins-workspace
[bug][notification] Windows: init script always sets `window.Notification.permission` to `"denied"` at startup
- Dominant language
- Rust
- Stars
- 1.8k
- Forks
- 602
- Avg merge
- 4d 14h
- Merged PRs (30d)
- 9
Description
## Describe the bug
On Windows, the notification plugin's injected init script stamps
`window.Notification.permission = "denied"` on every page load, even though the
desktop backend unconditionally reports `PermissionState::Granted` on Windows.
Any app that reads `window.Notification.permission` (or gates sending on it)
concludes notifications are blocked.
In [`guest-js/init.ts`](https://github.com/tauri-apps/plugins-workspace/blob/v2/plugins/notification/guest-js/init.ts#L12-L18):
```ts
async function isPermissionGranted(): Promise {
if (window.Notification.permission !== 'default' || __TEMPLATE_windows__) {
return await Promise.resolve(window.Notification.permission === 'granted')
}
return await invoke('plugin:notification|is_permission_granted')
}
```
By the time this runs, `window.Notification` has already been replaced by the shim
defined earlier in the same IIFE, whose `permission` is initialized to `'default'`.
With `__TEMPLATE_windows__ === true` the function therefore always returns
`'default' === 'granted'` → `false`, and the bootstrap at the bottom of the file maps
`false` → `'denied'`:
```ts
void isPermissionGranted().then(function (response) {
if (response === null) {
setNotificationPermission('default')
} else {
setNotificationPermission(response ? 'granted' : 'denied')
}
})
```
The Windows short-circuit presumably predates the shim replacement (it would make
sense reading the *native* WebView2 permission), but as written it can only ever
observe the shim's own initial value.
## Reproduction
Any Tauri v2 app on Windows with the notification plugin registered:
```js
console.log(window.Notification.permission) // "denied" — always, fresh profile
await window.Notification.requestPermission() // "granted"
console.log(window.Notification.permission) // "granted" — until next launch
```
Observed in the wild in block/buzz (block/buzz#2445), where the settings UI
reads `window.Notification.permission`, sees `"denied"`, and tells users to enable
notifications in system settings (where nothing is disabled).
## Expected behavior
On Windows the bootstrap should reflect the backend (`permission_state()` returns
`Granted`), i.e. initialize to `"granted"` — or fall through to
`invoke('plugin:notification|is_permission_granted')` like other platforms:
```ts
async function isPermissionGranted(): Promise {
if (window.Notification.permission !== 'default') {
return window.Notification.permission === 'granted'
}
return await invoke('plugin:notification|is_permission_granted')
}
```
## Platform and versions
- OS: Windows 11 (10.0.26200), WebView2 Evergreen
- tauri-plugin-notification: 2.3.3 (bug still present on `v2` branch tip)
- tauri: 2.x
Contributor guide
Research direction
Start in plugins/notification/guest-js/init.ts, reading the Notification shim, isPermissionGranted(), and the bootstrap that calls setNotificationPermission(). Compare this flow with the backend permission_state() behavior described in the issue. Done means a fresh Windows launch reflects the backend as granted and requestPermission() continues to report the expected state.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust, typescript
- Domain
- desktop
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100