From 1018dcdd4a8490ac90c92b5b37aa84a62cffd6ad Mon Sep 17 00:00:00 2001 From: Dawit Ameneshewa Date: Tue, 6 Dec 2022 10:11:45 +0300 Subject: [PATCH 1/7] Make `StorageApi` notify its subscribers when localStorage values change in other tabs/windows Signed-off-by: Dawit Ameneshewa --- .../src/apis/implementations/StorageApi/WebStorage.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts b/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts index b0cbfbd883..c17cc56494 100644 --- a/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts +++ b/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts @@ -89,9 +89,19 @@ export class WebStorage implements StorageApi { observe$( key: string, ): Observable> { + this.addStorageEventListener(); return this.observable.filter(({ key: messageKey }) => messageKey === key); } + private addStorageEventListener() { + window.addEventListener('storage', event => { + if (event.key?.includes(this.namespace)) { + const key = event.key.replace(`${this.namespace}/`, ''); + this.notifyChanges(key); + } + }); + } + private getKeyName(key: string) { return `${this.namespace}/${encodeURIComponent(key)}`; } From 653d7912acf8a0c29179f2fc4b1d227e9fed3065 Mon Sep 17 00:00:00 2001 From: Dawit Ameneshewa Date: Tue, 6 Dec 2022 10:29:37 +0300 Subject: [PATCH 2/7] Add changeset Signed-off-by: Dawit Ameneshewa --- .changeset/wild-pears-enjoy.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/wild-pears-enjoy.md diff --git a/.changeset/wild-pears-enjoy.md b/.changeset/wild-pears-enjoy.md new file mode 100644 index 0000000000..5b987e49dd --- /dev/null +++ b/.changeset/wild-pears-enjoy.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': patch +--- + +Made `StorageApi` notify its subscribers when localStorage values change in other tabs/windows From e2aaa67f62f93a948baca763dffd7f3ff989afb9 Mon Sep 17 00:00:00 2001 From: Dawit Ameneshewa Date: Tue, 6 Dec 2022 13:24:38 +0300 Subject: [PATCH 3/7] Fix failing docs quality check Signed-off-by: Dawit Ameneshewa --- .changeset/wild-pears-enjoy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/wild-pears-enjoy.md b/.changeset/wild-pears-enjoy.md index 5b987e49dd..8cee4daa54 100644 --- a/.changeset/wild-pears-enjoy.md +++ b/.changeset/wild-pears-enjoy.md @@ -2,4 +2,4 @@ '@backstage/core-app-api': patch --- -Made `StorageApi` notify its subscribers when localStorage values change in other tabs/windows +Made `StorageApi` notify its subscribers when `localStorage` values change in other tabs/windows From e5dec1b3864e108e7eaca8f6131c16c85290de0e Mon Sep 17 00:00:00 2001 From: Dawit Ameneshewa Date: Tue, 6 Dec 2022 17:53:26 +0300 Subject: [PATCH 4/7] Add only one, global storage event listener for all buckets Signed-off-by: Dawit Ameneshewa --- .../implementations/StorageApi/WebStorage.ts | 20 +++++++++++-------- .../src/apis/definitions/StorageApi.ts | 8 ++++++++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts b/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts index c17cc56494..0f56a608c1 100644 --- a/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts +++ b/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts @@ -24,6 +24,14 @@ import ObservableImpl from 'zen-observable'; const buckets = new Map(); +window.addEventListener('storage', event => { + for (const [bucketPath, webStorage] of buckets.entries()) { + if (event.key?.startsWith(bucketPath)) { + webStorage.handleStorageChange(event.key); + } + } +}); + /** * An implementation of the storage API, that uses the browser's local storage. * @@ -89,17 +97,13 @@ export class WebStorage implements StorageApi { observe$( key: string, ): Observable> { - this.addStorageEventListener(); return this.observable.filter(({ key: messageKey }) => messageKey === key); } - private addStorageEventListener() { - window.addEventListener('storage', event => { - if (event.key?.includes(this.namespace)) { - const key = event.key.replace(`${this.namespace}/`, ''); - this.notifyChanges(key); - } - }); + handleStorageChange(eventKey: StorageEvent['key']) { + if (!eventKey?.startsWith(this.namespace)) return; + const key = eventKey?.replace(`${this.namespace}/`, ''); + this.notifyChanges(key); } private getKeyName(key: string) { diff --git a/packages/core-plugin-api/src/apis/definitions/StorageApi.ts b/packages/core-plugin-api/src/apis/definitions/StorageApi.ts index 1506e5f143..f8c0948ec6 100644 --- a/packages/core-plugin-api/src/apis/definitions/StorageApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/StorageApi.ts @@ -85,6 +85,14 @@ export interface StorageApi { key: string, ): Observable>; + /** + * Synchronizes stored data across multiple tabs or windows by using the + * browser's StorageEvent API. + * + * @param eventKey - The key of a storage event object + */ + handleStorageChange(eventKey: StorageEvent['key']): void; + /** * Returns an immediate snapshot value for the given key, if possible. * From bc2f9ad271d9591e4b0368ef89f32225f4a8cb46 Mon Sep 17 00:00:00 2001 From: Dawit Ameneshewa Date: Wed, 7 Dec 2022 09:44:15 +0300 Subject: [PATCH 5/7] Add documentation for an optional `handleStorageChange` method in `StorageApi` Signed-off-by: Dawit Ameneshewa --- packages/core-app-api/api-report.md | 2 ++ packages/core-plugin-api/api-report.md | 1 + packages/core-plugin-api/src/apis/definitions/StorageApi.ts | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index 70312a54a5..54aa700472 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -558,6 +558,8 @@ export class WebStorage implements StorageApi { // (undocumented) get(key: string): T | undefined; // (undocumented) + handleStorageChange(eventKey: StorageEvent['key']): void; + // (undocumented) observe$( key: string, ): Observable>; diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md index cebf09e908..ed40792c6a 100644 --- a/packages/core-plugin-api/api-report.md +++ b/packages/core-plugin-api/api-report.md @@ -697,6 +697,7 @@ export type SignInPageProps = { // @public export interface StorageApi { forBucket(name: string): StorageApi; + handleStorageChange?(eventKey: StorageEvent['key']): void; observe$( key: string, ): Observable>; diff --git a/packages/core-plugin-api/src/apis/definitions/StorageApi.ts b/packages/core-plugin-api/src/apis/definitions/StorageApi.ts index f8c0948ec6..5ecf251e2d 100644 --- a/packages/core-plugin-api/src/apis/definitions/StorageApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/StorageApi.ts @@ -91,7 +91,7 @@ export interface StorageApi { * * @param eventKey - The key of a storage event object */ - handleStorageChange(eventKey: StorageEvent['key']): void; + handleStorageChange?(eventKey: StorageEvent['key']): void; /** * Returns an immediate snapshot value for the given key, if possible. From 5aa0dc604f772d3b825d057b3917f498b6965405 Mon Sep 17 00:00:00 2001 From: Dawit Ameneshewa Date: Wed, 7 Dec 2022 14:29:53 +0300 Subject: [PATCH 6/7] Add storage event listener only when there is at least one `WebStorage` API subscriber Signed-off-by: Dawit Ameneshewa --- .../implementations/StorageApi/WebStorage.ts | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts b/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts index 0f56a608c1..224327298f 100644 --- a/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts +++ b/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts @@ -24,14 +24,6 @@ import ObservableImpl from 'zen-observable'; const buckets = new Map(); -window.addEventListener('storage', event => { - for (const [bucketPath, webStorage] of buckets.entries()) { - if (event.key?.startsWith(bucketPath)) { - webStorage.handleStorageChange(event.key); - } - } -}); - /** * An implementation of the storage API, that uses the browser's local storage. * @@ -43,6 +35,8 @@ export class WebStorage implements StorageApi { private readonly errorApi: ErrorApi, ) {} + private static hasSubscribers = false; + static create(options: { errorApi: ErrorApi; namespace?: string; @@ -50,6 +44,16 @@ export class WebStorage implements StorageApi { return new WebStorage(options.namespace ?? '', options.errorApi); } + private static addStorageEventListener() { + window.addEventListener('storage', event => { + for (const [bucketPath, webStorage] of buckets.entries()) { + if (event.key?.startsWith(bucketPath)) { + webStorage.handleStorageChange(event.key); + } + } + }); + } + get(key: string): T | undefined { return this.snapshot(key).value as T | undefined; } @@ -97,6 +101,10 @@ export class WebStorage implements StorageApi { observe$( key: string, ): Observable> { + if (!WebStorage.hasSubscribers) { + WebStorage.addStorageEventListener(); + WebStorage.hasSubscribers = true; + } return this.observable.filter(({ key: messageKey }) => messageKey === key); } From 1dec14de936bf550d5252b34055f5020c500fb29 Mon Sep 17 00:00:00 2001 From: Dawit Ameneshewa Date: Wed, 7 Dec 2022 16:45:32 +0300 Subject: [PATCH 7/7] Make `handleStorageChange` method in `WebStorage` class private Signed-off-by: Dawit Ameneshewa --- .changeset/wild-pears-enjoy.md | 2 +- packages/core-app-api/api-report.md | 2 -- .../src/apis/implementations/StorageApi/WebStorage.ts | 8 ++++---- packages/core-plugin-api/api-report.md | 1 - .../core-plugin-api/src/apis/definitions/StorageApi.ts | 8 -------- 5 files changed, 5 insertions(+), 16 deletions(-) diff --git a/.changeset/wild-pears-enjoy.md b/.changeset/wild-pears-enjoy.md index 8cee4daa54..01a8b8a4a1 100644 --- a/.changeset/wild-pears-enjoy.md +++ b/.changeset/wild-pears-enjoy.md @@ -2,4 +2,4 @@ '@backstage/core-app-api': patch --- -Made `StorageApi` notify its subscribers when `localStorage` values change in other tabs/windows +Made `WebStorage` notify its subscribers when `localStorage` values change in other tabs/windows diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index 54aa700472..70312a54a5 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -558,8 +558,6 @@ export class WebStorage implements StorageApi { // (undocumented) get(key: string): T | undefined; // (undocumented) - handleStorageChange(eventKey: StorageEvent['key']): void; - // (undocumented) observe$( key: string, ): Observable>; diff --git a/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts b/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts index 224327298f..3477853241 100644 --- a/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts +++ b/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts @@ -35,7 +35,7 @@ export class WebStorage implements StorageApi { private readonly errorApi: ErrorApi, ) {} - private static hasSubscribers = false; + private static hasSubscribed = false; static create(options: { errorApi: ErrorApi; @@ -101,14 +101,14 @@ export class WebStorage implements StorageApi { observe$( key: string, ): Observable> { - if (!WebStorage.hasSubscribers) { + if (!WebStorage.hasSubscribed) { WebStorage.addStorageEventListener(); - WebStorage.hasSubscribers = true; + WebStorage.hasSubscribed = true; } return this.observable.filter(({ key: messageKey }) => messageKey === key); } - handleStorageChange(eventKey: StorageEvent['key']) { + private handleStorageChange(eventKey: StorageEvent['key']) { if (!eventKey?.startsWith(this.namespace)) return; const key = eventKey?.replace(`${this.namespace}/`, ''); this.notifyChanges(key); diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md index ed40792c6a..cebf09e908 100644 --- a/packages/core-plugin-api/api-report.md +++ b/packages/core-plugin-api/api-report.md @@ -697,7 +697,6 @@ export type SignInPageProps = { // @public export interface StorageApi { forBucket(name: string): StorageApi; - handleStorageChange?(eventKey: StorageEvent['key']): void; observe$( key: string, ): Observable>; diff --git a/packages/core-plugin-api/src/apis/definitions/StorageApi.ts b/packages/core-plugin-api/src/apis/definitions/StorageApi.ts index 5ecf251e2d..1506e5f143 100644 --- a/packages/core-plugin-api/src/apis/definitions/StorageApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/StorageApi.ts @@ -85,14 +85,6 @@ export interface StorageApi { key: string, ): Observable>; - /** - * Synchronizes stored data across multiple tabs or windows by using the - * browser's StorageEvent API. - * - * @param eventKey - The key of a storage event object - */ - handleStorageChange?(eventKey: StorageEvent['key']): void; - /** * Returns an immediate snapshot value for the given key, if possible. *