From c1494b8494a5590c747c7ff28fee3d7bb0d7264f Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Fri, 20 Oct 2023 18:41:26 +0200 Subject: [PATCH] [PM-4401] Re-add manual ngZone.run execution (#6647) * [PM-4401] feat: add browser messaging api service * [PM-4401] feat: use new service * [PM-4401] chore: rename to `ZonedMessageListenerService` * [PM-4401] chore: remove polyfill --- .../browser/run-inside-angular.operator.ts | 21 +++++++++ .../browser/zoned-message-listener.service.ts | 33 ++++++++++++++ .../polyfills/zone-patch-chrome-runtime.ts | 44 ------------------- apps/browser/src/popup/app.component.ts | 6 ++- apps/browser/src/popup/polyfills.ts | 2 - .../popup/components/fido2/fido2.component.ts | 10 +++-- 6 files changed, 65 insertions(+), 51 deletions(-) create mode 100644 apps/browser/src/platform/browser/run-inside-angular.operator.ts create mode 100644 apps/browser/src/platform/browser/zoned-message-listener.service.ts delete mode 100644 apps/browser/src/platform/polyfills/zone-patch-chrome-runtime.ts diff --git a/apps/browser/src/platform/browser/run-inside-angular.operator.ts b/apps/browser/src/platform/browser/run-inside-angular.operator.ts new file mode 100644 index 0000000000..4e9b52b009 --- /dev/null +++ b/apps/browser/src/platform/browser/run-inside-angular.operator.ts @@ -0,0 +1,21 @@ +import { NgZone } from "@angular/core"; +import { MonoTypeOperatorFunction, Observable } from "rxjs"; + +export function runInsideAngular(ngZone: NgZone): MonoTypeOperatorFunction { + return (source: Observable) => + new Observable((subscriber) => { + const subscription = source.subscribe({ + next(value) { + ngZone.run(() => subscriber.next(value)); + }, + error(error: unknown) { + ngZone.run(() => subscriber.error(error)); + }, + complete() { + ngZone.run(() => subscriber.complete()); + }, + }); + + return () => subscription.unsubscribe(); + }); +} diff --git a/apps/browser/src/platform/browser/zoned-message-listener.service.ts b/apps/browser/src/platform/browser/zoned-message-listener.service.ts new file mode 100644 index 0000000000..4f8d740d23 --- /dev/null +++ b/apps/browser/src/platform/browser/zoned-message-listener.service.ts @@ -0,0 +1,33 @@ +import { Injectable, NgZone } from "@angular/core"; +import { Observable } from "rxjs"; + +import { BrowserApi } from "./browser-api"; +import { runInsideAngular } from "./run-inside-angular.operator"; + +/** + * This service is used for listening to messages from the background script. + * It automatically runs all callbacks inside the Angular zone. + * This should be used instead of `BrowserApi.messageListener` in all popup-components. + * Not needed for services running in the background script. + */ +@Injectable({ providedIn: "root" }) +export class ZonedMessageListenerService { + constructor(private ngZone: NgZone) {} + + messageListener( + name: string, + callback: ( + message: any, + sender: chrome.runtime.MessageSender, + sendResponse: any + ) => boolean | void + ) { + BrowserApi.messageListener(name, (message, sender, sendResponse) => { + return this.ngZone.run(() => callback(message, sender, sendResponse)); + }); + } + + messageListener$(): Observable { + return BrowserApi.messageListener$().pipe(runInsideAngular(this.ngZone)); + } +} diff --git a/apps/browser/src/platform/polyfills/zone-patch-chrome-runtime.ts b/apps/browser/src/platform/polyfills/zone-patch-chrome-runtime.ts deleted file mode 100644 index 055518fc75..0000000000 --- a/apps/browser/src/platform/polyfills/zone-patch-chrome-runtime.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Monkey patch `chrome.runtime.onMessage` event listeners to run in the Angular zone. - */ -Zone.__load_patch("ChromeRuntimeOnMessage", (global: any, Zone: ZoneType, api: _ZonePrivate) => { - if (typeof global?.chrome?.runtime?.onMessage === "undefined") { - return; - } - const onMessage = global.chrome.runtime.onMessage; - - // eslint-disable-next-line @typescript-eslint/ban-types - const nativeAddListener = onMessage.addListener as Function; - api.ObjectDefineProperty(chrome.runtime.onMessage, "addListener", { - value: function (...args: any[]) { - const callback = args.length > 0 ? args[0] : null; - if (typeof callback === "function") { - const wrapperedCallback = Zone.current.wrap(callback, "ChromeRuntimeOnMessage"); - callback[api.symbol("chromeRuntimeOnMessageCallback")] = wrapperedCallback; - return nativeAddListener.call(onMessage, wrapperedCallback); - } else { - return nativeAddListener.apply(onMessage, args); - } - }, - writable: false, - }); - - // eslint-disable-next-line @typescript-eslint/ban-types - const nativeRemoveListener = onMessage.removeListener as Function; - api.ObjectDefineProperty(chrome.runtime.onMessage, "removeListener", { - value: function (...args: any[]) { - const callback = args.length > 0 ? args[0] : null; - if (typeof callback === "function") { - const wrapperedCallback = callback[api.symbol("chromeRuntimeOnMessageCallback")]; - if (wrapperedCallback) { - return nativeRemoveListener.call(onMessage, wrapperedCallback); - } else { - return nativeRemoveListener.apply(onMessage, args); - } - } else { - return nativeRemoveListener.apply(onMessage, args); - } - }, - writable: false, - }); -}); diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index 16ef77ed8f..9d91bef0ba 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -19,6 +19,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { DialogService, SimpleDialogOptions } from "@bitwarden/components"; import { BrowserApi } from "../platform/browser/browser-api"; +import { ZonedMessageListenerService } from "../platform/browser/zoned-message-listener.service"; import { BrowserStateService } from "../platform/services/abstractions/browser-state.service"; import { routerTransition } from "./app-routing.animations"; @@ -50,7 +51,8 @@ export class AppComponent implements OnInit, OnDestroy { private ngZone: NgZone, private sanitizer: DomSanitizer, private platformUtilsService: PlatformUtilsService, - private dialogService: DialogService + private dialogService: DialogService, + private browserMessagingApi: ZonedMessageListenerService ) {} async ngOnInit() { @@ -128,7 +130,7 @@ export class AppComponent implements OnInit, OnDestroy { }; (window as any).bitwardenPopupMainMessageListener = bitwardenPopupMainMessageListener; - BrowserApi.messageListener("app.component", bitwardenPopupMainMessageListener); + this.browserMessagingApi.messageListener("app.component", bitwardenPopupMainMessageListener); // eslint-disable-next-line rxjs/no-async-subscribe this.router.events.pipe(takeUntil(this.destroy$)).subscribe(async (event) => { diff --git a/apps/browser/src/popup/polyfills.ts b/apps/browser/src/popup/polyfills.ts index 14c0f595a3..f76b9e632f 100644 --- a/apps/browser/src/popup/polyfills.ts +++ b/apps/browser/src/popup/polyfills.ts @@ -1,4 +1,2 @@ import "core-js/stable"; import "zone.js"; - -import "../platform/polyfills/zone-patch-chrome-runtime"; diff --git a/apps/browser/src/vault/popup/components/fido2/fido2.component.ts b/apps/browser/src/vault/popup/components/fido2/fido2.component.ts index b4ad5e5a52..b5247169aa 100644 --- a/apps/browser/src/vault/popup/components/fido2/fido2.component.ts +++ b/apps/browser/src/vault/popup/components/fido2/fido2.component.ts @@ -30,7 +30,7 @@ import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note. import { DialogService } from "@bitwarden/components"; import { PasswordRepromptService } from "@bitwarden/vault"; -import { BrowserApi } from "../../../../platform/browser/browser-api"; +import { ZonedMessageListenerService } from "../../../../platform/browser/zoned-message-listener.service"; import { BrowserFido2Message, BrowserFido2UserInterfaceSession, @@ -78,7 +78,8 @@ export class Fido2Component implements OnInit, OnDestroy { private settingsService: SettingsService, private searchService: SearchService, private logService: LogService, - private dialogService: DialogService + private dialogService: DialogService, + private browserMessagingApi: ZonedMessageListenerService ) {} ngOnInit() { @@ -93,7 +94,10 @@ export class Fido2Component implements OnInit, OnDestroy { })) ); - combineLatest([queryParams$, BrowserApi.messageListener$() as Observable]) + combineLatest([ + queryParams$, + this.browserMessagingApi.messageListener$() as Observable, + ]) .pipe( concatMap(async ([queryParams, message]) => { this.sessionId = queryParams.sessionId;