From 975a11b37f6a786c9c979cab97bd0d8bd1be31e6 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Wed, 19 Feb 2020 18:58:33 +0200 Subject: [PATCH] fix(docs-infra): do not break when cookies are disabled in the browser (#35557) Whenever cookies are disabled in the browser, `window.localStorage` is not avaialable to the app (i.e. even trying to access `window.localStorage` throws an error). To avoid breaking the app, we use a no-op `Storage` implementation if `window.localStorage` is not available. (This is similar to #33829, but for `localStorage` instead of `sessionStorage`.) Fixes #35555 PR Close #35557 --- .../notification.component.spec.ts | 16 ++++++++++++ .../notification/notification.component.ts | 25 +++++++++++++++---- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/aio/src/app/layout/notification/notification.component.spec.ts b/aio/src/app/layout/notification/notification.component.spec.ts index 069eff11e5..4cdfbe3ef3 100644 --- a/aio/src/app/layout/notification/notification.component.spec.ts +++ b/aio/src/app/layout/notification/notification.component.spec.ts @@ -111,6 +111,22 @@ describe('NotificationComponent', () => { expect(getItemSpy).toHaveBeenCalledWith('aio-notification/survey-january-2018'); expect(component.showNotification).toBe('hide'); }); + + it('should not break when cookies are disabled in the browser', () => { + configTestingModule(); + + // Simulate `window.localStorage` being inaccessible, when cookies are disabled. + const mockWindow: MockWindow = TestBed.inject(WindowToken); + Object.defineProperty(mockWindow, 'localStorage', { + get() { throw new Error('The operation is insecure'); }, + }); + + expect(() => createComponent()).not.toThrow(); + expect(component.showNotification).toBe('show'); + + component.dismiss(); + expect(component.showNotification).toBe('hide'); + }); }); @Component({ diff --git a/aio/src/app/layout/notification/notification.component.ts b/aio/src/app/layout/notification/notification.component.ts index 586b9cdf8b..868f69e284 100644 --- a/aio/src/app/layout/notification/notification.component.ts +++ b/aio/src/app/layout/notification/notification.component.ts @@ -20,7 +20,7 @@ const LOCAL_STORAGE_NAMESPACE = 'aio-notification/'; ] }) export class NotificationComponent implements OnInit { - private get localStorage() { return this.window.localStorage; } + private storage: Storage; @Input() dismissOnContentClick: boolean; @Input() notificationId: string; @@ -31,12 +31,27 @@ export class NotificationComponent implements OnInit { showNotification: 'show'|'hide'; constructor( - @Inject(WindowToken) private window: Window, + @Inject(WindowToken) window: Window, @Inject(CurrentDateToken) private currentDate: Date - ) {} + ) { + try { + this.storage = window.localStorage; + } catch { + // When cookies are disabled in the browser, even trying to access + // `window.localStorage` throws an error. Use a no-op storage. + this.storage = { + length: 0, + clear: () => undefined, + getItem: () => null, + key: () => null, + removeItem: () => undefined, + setItem: () => undefined + }; + } + } ngOnInit() { - const previouslyHidden = this.localStorage.getItem(LOCAL_STORAGE_NAMESPACE + this.notificationId) === 'hide'; + const previouslyHidden = this.storage.getItem(LOCAL_STORAGE_NAMESPACE + this.notificationId) === 'hide'; const expired = this.currentDate > new Date(this.expirationDate); this.showNotification = previouslyHidden || expired ? 'hide' : 'show'; } @@ -48,7 +63,7 @@ export class NotificationComponent implements OnInit { } dismiss() { - this.localStorage.setItem(LOCAL_STORAGE_NAMESPACE + this.notificationId, 'hide'); + this.storage.setItem(LOCAL_STORAGE_NAMESPACE + this.notificationId, 'hide'); this.showNotification = 'hide'; this.dismissed.next(); }