From 04555cc62c9cded08c3090288fa372d961c50737 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Mon, 28 Mar 2022 23:37:15 +0200 Subject: feat: New window banner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add renderer code for notification banners with buttons * Handle new window open requests by denying them and displaying a notification Signed-off-by: Kristóf Marussy --- .../electron/impl/ElectronServiceView.ts | 10 +++++ packages/main/src/stores/Service.ts | 51 ++++++++++++++++++++++ 2 files changed, 61 insertions(+) (limited to 'packages/main') diff --git a/packages/main/src/infrastructure/electron/impl/ElectronServiceView.ts b/packages/main/src/infrastructure/electron/impl/ElectronServiceView.ts index edcf758..089e63a 100644 --- a/packages/main/src/infrastructure/electron/impl/ElectronServiceView.ts +++ b/packages/main/src/infrastructure/electron/impl/ElectronServiceView.ts @@ -136,6 +136,16 @@ export default class ElectronServiceView implements ServiceView { const { reason, exitCode } = details; service.setCrashed(reason, exitCode); }); + + webContents.setWindowOpenHandler(({ url }) => { + // TODO Add filtering (allowlist) by URL. + // TODO Handle `new-window` disposition where the service wants an object returned by + // `window.open`. + // TODO Handle downloads with `save-to-disk` disposition. + // TODO Handle POST bodies where the window must be allowed to open or the data is lost. + service.addBlockedPopup(url); + return { action: 'deny' }; + }); } get webContentsId(): number { diff --git a/packages/main/src/stores/Service.ts b/packages/main/src/stores/Service.ts index 26e3517..0a35114 100644 --- a/packages/main/src/stores/Service.ts +++ b/packages/main/src/stores/Service.ts @@ -105,6 +105,27 @@ const Service = defineServiceModel(ServiceSettings) } getEnv(self).openURLInExternalBrowser(self.currentUrl); }, + addBlockedPopup(url: string): void { + const index = self.popups.indexOf(url); + if (index >= 0) { + // Move existing popup to the end of the array, + // because later popups have precedence over earlier ones. + self.popups.splice(index, 1); + } + self.popups.push(url); + }, + dismissPopup(url: string): boolean { + const index = self.popups.indexOf(url); + if (index < 0) { + log.warn('Service', self.id, 'has no pending popup', url); + return false; + } + self.popups.splice(index, 1); + return true; + }, + dismissAllPopups(): void { + self.popups.splice(0); + }, })) .actions((self) => { function setState(state: ServiceStateSnapshotIn): void { @@ -190,6 +211,21 @@ const Service = defineServiceModel(ServiceSettings) self.state.trust = 'accepted'; self.reload(); }, + followPopup(url: string): void { + if (self.dismissPopup(url)) { + self.go(url); + } + }, + openPopupInExternalBrowser(url: string): void { + if (self.dismissPopup(url)) { + getEnv(self).openURLInExternalBrowser(url); + } + }, + openAllPopupsInExternalBrowser(): void { + const env = getEnv(self); + self.popups.forEach((popup) => env.openURLInExternalBrowser(popup)); + self.dismissAllPopups(); + }, })) .actions((self) => ({ dispatch(action: ServiceAction): void { @@ -218,6 +254,21 @@ const Service = defineServiceModel(ServiceSettings) case 'open-current-url-in-external-browser': self.openCurrentURLInExternalBrowser(); break; + case 'follow-popup': + self.followPopup(action.url); + break; + case 'open-popup-in-external-browser': + self.openPopupInExternalBrowser(action.url); + break; + case 'open-all-popups-in-external-browser': + self.openAllPopupsInExternalBrowser(); + break; + case 'dismiss-popup': + self.dismissPopup(action.url); + break; + case 'dismiss-all-popups': + self.dismissAllPopups(); + break; default: log.error('Unknown action to dispatch', action); break; -- cgit v1.2.3-54-g00ecf