Skip to content

Commit

Permalink
In webview, send desktop notification via Beeep
Browse files Browse the repository at this point in the history
  • Loading branch information
peterzen committed Dec 12, 2023
1 parent d05fcb5 commit 8591fe6
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 58 deletions.
4 changes: 4 additions & 0 deletions client/cmd/dexc-desktop/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,10 @@ func bindJSFunctions(w webview.WebView) {
log.Errorf("unable to run URL handler: %s", err.Error())
}
})

w.Bind("sendOSNotification", func(title, body string) {
sendDesktopNotification(title, body)
})
}

func runWebview(url string) {
Expand Down
8 changes: 4 additions & 4 deletions client/webserver/site/src/js/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,8 @@ export default class Application {
this.attachCommon(this.header)
this.attach({})
this.updateMenuItemsDisplay()
// initialize browser notifications
ntfn.fetchBrowserNtfnSettings()
// initialize desktop notifications
ntfn.fetchDesktopNtfnSettings()
// Load recent notifications from Window.localStorage.
const notes = State.fetchLocal(State.notificationsLK)
this.setNotes(notes || [])
Expand Down Expand Up @@ -715,8 +715,8 @@ export default class Application {
if (note.severity === ntfn.POKE) this.prependPokeElement(note)
else this.prependNoteElement(note)

// show browser notification
ntfn.browserNotify(note)
// show desktop notification
ntfn.desktopNotify(note)
}

/*
Expand Down
110 changes: 70 additions & 40 deletions client/webserver/site/src/js/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,79 +35,109 @@ const NoteTypeMatch = 'match'
const NoteTypeBondPost = 'bondpost'
const NoteTypeConnEvent = 'conn'

type BrowserNtfnSettingLabel = {
type DesktopNtfnSettingLabel = {
[x: string]: string
}

type BrowserNtfnSetting = {
export type DesktopNtfnSetting = {
[x: string]: boolean
}

function browserNotificationsSettingsKey (): string {
return `browser_notifications-${window.location.host}`
function desktopNtfnSettingsKey (): string {
return `desktop_notifications-${window.location.host}`
}

export const browserNtfnLabels: BrowserNtfnSettingLabel = {
export const desktopNtfnLabels: DesktopNtfnSettingLabel = {
[NoteTypeOrder]: intl.ID_BROWSER_NTFN_ORDERS,
[NoteTypeMatch]: intl.ID_BROWSER_NTFN_MATCHES,
[NoteTypeBondPost]: intl.ID_BROWSER_NTFN_BONDS,
[NoteTypeConnEvent]: intl.ID_BROWSER_NTFN_CONNECTIONS
}

export const defaultBrowserNtfnSettings: BrowserNtfnSetting = {
export const defaultDesktopNtfnSettings: DesktopNtfnSetting = {
[NoteTypeOrder]: true,
[NoteTypeMatch]: true,
[NoteTypeBondPost]: true,
[NoteTypeConnEvent]: true
}

let browserNtfnSettings: BrowserNtfnSetting
let desktopNtfnSettings: DesktopNtfnSetting

export function ntfnPermissionGranted () {
return window.Notification.permission === 'granted'
}
// BrowserNotifier is a wrapper around the browser's notification API.
class BrowserNotifier {
static ntfnPermissionGranted (): boolean {
return window.Notification.permission === 'granted'
}

export function ntfnPermissionDenied () {
return window.Notification.permission === 'denied'
}
static ntfnPermissionDenied (): boolean {
return window.Notification.permission === 'denied'
}

export async function requestNtfnPermission () {
if (!('Notification' in window)) {
return
static async requestNtfnPermission (): Promise<void> {
if (!('Notification' in window)) {
return
}
if (BrowserNotifier.ntfnPermissionGranted()) {
BrowserNotifier.sendDesktopNotification(intl.prep(intl.ID_BROWSER_NTFN_ENABLED))
} else if (!BrowserNotifier.ntfnPermissionDenied()) {
await Notification.requestPermission()
BrowserNotifier.sendDesktopNotification(intl.prep(intl.ID_BROWSER_NTFN_ENABLED))
}
}
if (Notification.permission === 'granted') {
showBrowserNtfn(intl.prep(intl.ID_BROWSER_NTFN_ENABLED))
} else if (Notification.permission !== 'denied') {
await Notification.requestPermission()
showBrowserNtfn(intl.prep(intl.ID_BROWSER_NTFN_ENABLED))

static sendDesktopNotification (title: string, body?: string) {
if (!BrowserNotifier.ntfnPermissionGranted()) return
const ntfn = new window.Notification(title, {
body: body,
icon: '/img/softened-icon.png'
})
return ntfn
}
}

export function showBrowserNtfn (title: string, body?: string) {
if (window.Notification.permission !== 'granted') return
const ntfn = new window.Notification(title, {
body: body,
icon: '/img/softened-icon.png'
})
return ntfn
// OSDesktopNotifier manages OS desktop notifications via the same interface
// as BrowserNotifier, but sends notifications using an underlying Go
// notification library exposed to the webview.
class OSDesktopNotifier {
static ntfnPermissionGranted (): boolean {
return true
}

static ntfnPermissionDenied (): boolean {
return false
}

static async requestNtfnPermission (): Promise<void> {
OSDesktopNotifier.sendDesktopNotification(intl.prep(intl.ID_BROWSER_NTFN_ENABLED))
return Promise.resolve()
}

static sendDesktopNotification (title: string, body?: string): void {
// this calls a function exported via webview.Bind()
window.sendOSNotification(title, body)
}
}

export function browserNotify (note: CoreNote) {
if (!browserNtfnSettings[note.type]) return
showBrowserNtfn(note.subject, note.details)
// determine whether we're running in a webview or in browser, and export
// the appropriate notifier accordingly.
export const Notifier = window.isWebview ? OSDesktopNotifier : BrowserNotifier

export function desktopNotify (note: CoreNote) {
if (!desktopNtfnSettings.browserNtfnEnabled || !desktopNtfnSettings[note.type]) return
Notifier.sendDesktopNotification(note.subject, note.details)
}

export async function fetchBrowserNtfnSettings (): Promise<BrowserNtfnSetting> {
if (browserNtfnSettings !== undefined) {
return browserNtfnSettings
export function fetchDesktopNtfnSettings (): DesktopNtfnSetting {
if (desktopNtfnSettings !== undefined) {
return desktopNtfnSettings
}
const k = browserNotificationsSettingsKey()
browserNtfnSettings = (await State.fetchLocal(k) ?? {}) as BrowserNtfnSetting
return browserNtfnSettings
const k = desktopNtfnSettingsKey()
desktopNtfnSettings = (State.fetchLocal(k) ?? {}) as DesktopNtfnSetting
return desktopNtfnSettings
}

export async function updateNtfnSetting (noteType: string, enabled: boolean) {
await fetchBrowserNtfnSettings()
browserNtfnSettings[noteType] = enabled
State.storeLocal(browserNotificationsSettingsKey(), browserNtfnSettings)
fetchDesktopNtfnSettings()
desktopNtfnSettings[noteType] = enabled
State.storeLocal(desktopNtfnSettingsKey(), desktopNtfnSettings)
}
1 change: 1 addition & 0 deletions client/webserver/site/src/js/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ declare global {
user: () => User
isWebview?: () => boolean
openUrl: (url: string) => void
sendOSNotification (title: string, body?: string): void
}
}

Expand Down
26 changes: 12 additions & 14 deletions client/webserver/site/src/js/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,25 +175,23 @@ export default class SettingsPage extends BasePage {
this.renderDesktopNtfnSettings()
}

async updateNtfnSetting (e: Event) {
updateNtfnSetting (e: Event) {
const checkbox = e.target as HTMLInputElement
const noteType = checkbox.getAttribute('name')
if (noteType === null) return
const enabled = checkbox.checked
await ntfn.updateNtfnSetting(noteType, enabled)
ntfn.updateNtfnSetting(noteType, enabled)
}

async getBrowserNtfnSettings (form: HTMLElement) {
const loaded = app().loading(form)
const permissions = await ntfn.fetchBrowserNtfnSettings()
loaded()
getBrowserNtfnSettings (): ntfn.DesktopNtfnSetting {
const permissions = ntfn.fetchDesktopNtfnSettings()
return permissions
}

async renderDesktopNtfnSettings () {
const page = this.page
const ntfnSettings = await this.getBrowserNtfnSettings(page.browserNotificationsForm)
const labels = ntfn.browserNtfnLabels
const ntfnSettings = this.getBrowserNtfnSettings()
const labels = ntfn.desktopNtfnLabels
const tmpl = page.browserNtfnCheckboxTemplate
tmpl.removeAttribute('id')
const container = page.browserNtfnCheckboxContainer
Expand All @@ -213,25 +211,25 @@ export default class SettingsPage extends BasePage {
const enabledCheckbox = page.browserNtfnEnabled

Doc.bind(enabledCheckbox, 'click', async (e: Event) => {
if (ntfn.ntfnPermissionDenied()) return
if (ntfn.Notifier.ntfnPermissionDenied()) return
const checkbox = e.target as HTMLInputElement
if (checkbox.checked) {
await ntfn.requestNtfnPermission()
checkbox.checked = !ntfn.ntfnPermissionDenied()
await ntfn.Notifier.requestNtfnPermission()
checkbox.checked = !ntfn.Notifier.ntfnPermissionDenied()
}
await this.updateNtfnSetting(e)
this.updateNtfnSetting(e)
checkbox.dispatchEvent(new Event('change'))
})

Doc.bind(enabledCheckbox, 'change', (e: Event) => {
const checkbox = e.target as HTMLInputElement
const permDenied = ntfn.ntfnPermissionDenied()
const permDenied = ntfn.Notifier.ntfnPermissionDenied()
Doc.setVis(checkbox.checked, page.browserNtfnCheckboxContainer)
Doc.setVis(permDenied, page.browserNtfnBlockedMsg)
checkbox.disabled = permDenied
})

enabledCheckbox.checked = (ntfn.ntfnPermissionGranted() && ntfnSettings.browserNtfnEnabled)
enabledCheckbox.checked = (ntfn.Notifier.ntfnPermissionGranted() && ntfnSettings.browserNtfnEnabled)
enabledCheckbox.dispatchEvent(new Event('change'))
}

Expand Down

0 comments on commit 8591fe6

Please sign in to comment.