diff --git a/npm/ng-packs/packages/oauth/ng-package.json b/npm/ng-packs/packages/oauth/ng-package.json index f9cb029dc03..79f6d635312 100644 --- a/npm/ng-packs/packages/oauth/ng-package.json +++ b/npm/ng-packs/packages/oauth/ng-package.json @@ -4,6 +4,13 @@ "lib": { "entryFile": "src/public-api.ts" }, + "assets": [ + { + "glob": "token-storage.worker.js", + "input": "src/lib/workers", + "output": "workers" + } + ], "allowedNonPeerDependencies": [ "@abp/utils", "@abp/ng.core", diff --git a/npm/ng-packs/packages/oauth/src/lib/services/index.ts b/npm/ng-packs/packages/oauth/src/lib/services/index.ts index 32909341a1e..9e8637a59a8 100644 --- a/npm/ng-packs/packages/oauth/src/lib/services/index.ts +++ b/npm/ng-packs/packages/oauth/src/lib/services/index.ts @@ -3,3 +3,4 @@ export * from './oauth-error-filter.service'; export * from './remember-me.service'; export * from './browser-token-storage.service'; export * from './server-token-storage.service'; +export * from './memory-token-storage.service'; diff --git a/npm/ng-packs/packages/oauth/src/lib/services/memory-token-storage.service.ts b/npm/ng-packs/packages/oauth/src/lib/services/memory-token-storage.service.ts new file mode 100644 index 00000000000..45e09b86529 --- /dev/null +++ b/npm/ng-packs/packages/oauth/src/lib/services/memory-token-storage.service.ts @@ -0,0 +1,122 @@ +import { DOCUMENT, inject, Injectable } from '@angular/core'; +import { OAuthStorage } from 'angular-oauth2-oidc'; +import { AbpLocalStorageService } from '@abp/ng.core'; + +@Injectable({ + providedIn: 'root', +}) +export class MemoryTokenStorageService implements OAuthStorage { + private keysShouldStoreInMemory = [ + 'access_token', 'id_token', 'expires_at', + 'id_token_claims_obj', 'id_token_expires_at', + 'id_token_stored_at', 'access_token_stored_at', + 'abpOAuthClientId', 'granted_scopes' + ]; + + private worker?: any; + private port?: MessagePort; + private cache = new Map(); + private localStorageService = inject(AbpLocalStorageService); + private _document = inject(DOCUMENT); + private useSharedWorker = false; + + constructor() { + this.initializeStorage(); + } + + private initializeStorage(): void { + // @ts-ignore + if (typeof SharedWorker !== 'undefined') { + try { + // @ts-ignore + this.worker = new SharedWorker( + new URL( + '../workers/token-storage.worker.js', + import.meta.url + ), + { name: 'oauth-token-storage', type: "module" } + ); + + this.port = this.worker.port; + this.port.start(); + this.useSharedWorker = true; + + this.port.onmessage = (event) => { + const { action, key, value } = event.data; + + switch (action) { + case 'set': + this.checkAuthStateChanges(key); + this.cache.set(key, value); + break; + case 'remove': + this.cache.delete(key); + this.refreshDocument(); + break; + case 'clear': + this.cache.clear(); + this.refreshDocument(); + break; + } + }; + } catch (error) { + this.useSharedWorker = false; + } + } else { + this.useSharedWorker = false; + } + } + + getItem(key: string): string | null { + if (!this.keysShouldStoreInMemory.includes(key)) { + return this.localStorageService.getItem(key); + } + return this.cache.get(key) || null; + } + + setItem(key: string, value: string): void { + if (!this.keysShouldStoreInMemory.includes(key)) { + this.localStorageService.setItem(key, value); + return; + } + + if (this.useSharedWorker && this.port) { + this.cache.set(key, value); + this.port.postMessage({ action: 'set', key, value }); + } else { + this.cache.set(key, value); + } + } + + removeItem(key: string): void { + if (!this.keysShouldStoreInMemory.includes(key)) { + this.localStorageService.removeItem(key); + return; + } + + if (this.useSharedWorker && this.port) { + this.cache.delete(key); + this.port.postMessage({ action: 'remove', key }); + } else { + this.cache.delete(key); + } + } + + clear(): void { + if (this.useSharedWorker && this.port) { + this.port.postMessage({ action: 'clear' }); + } + this.cache.clear(); + } + + private checkAuthStateChanges = (key: string) => { + if (key === 'access_token' && !this.cache.get('access_token')) { + this.refreshDocument(); + } + } + + private refreshDocument(): void { + this._document.defaultView?.location.reload(); + } + +} diff --git a/npm/ng-packs/packages/oauth/src/lib/services/oauth.service.ts b/npm/ng-packs/packages/oauth/src/lib/services/oauth.service.ts index a25c3f61389..8a30b098e17 100644 --- a/npm/ng-packs/packages/oauth/src/lib/services/oauth.service.ts +++ b/npm/ng-packs/packages/oauth/src/lib/services/oauth.service.ts @@ -110,7 +110,12 @@ export class AbpOAuthService implements IAuthService { this.document.defaultView?.location.replace('/authorize'); return Promise.resolve(); } - return this.oAuthService.refreshToken(); + try { + return this.oAuthService.refreshToken(); + } catch (error) { + console.log("Error while refreshing token: ", error); + return Promise.reject(); + } } getAccessTokenExpiration(): number { diff --git a/npm/ng-packs/packages/oauth/src/lib/utils/storage.factory.ts b/npm/ng-packs/packages/oauth/src/lib/utils/storage.factory.ts index 97cd86682b0..8a2f7e04c55 100644 --- a/npm/ng-packs/packages/oauth/src/lib/utils/storage.factory.ts +++ b/npm/ng-packs/packages/oauth/src/lib/utils/storage.factory.ts @@ -1,9 +1,9 @@ import { inject, PLATFORM_ID } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; import { ServerTokenStorageService } from '../services/server-token-storage.service'; -import { BrowserTokenStorageService } from '../services'; +import { BrowserTokenStorageService, MemoryTokenStorageService } from '../services'; import { OAuthStorage } from 'angular-oauth2-oidc'; -import { AbpLocalStorageService, APP_STARTED_WITH_SSR } from '@abp/ng.core'; +import { APP_STARTED_WITH_SSR } from '@abp/ng.core'; export class MockStorage implements Storage { private data = new Map(); @@ -35,5 +35,5 @@ export function oAuthStorageFactory(): OAuthStorage { ? inject(BrowserTokenStorageService) : inject(ServerTokenStorageService); } - return inject(AbpLocalStorageService); + return inject(MemoryTokenStorageService); } diff --git a/npm/ng-packs/packages/oauth/src/lib/workers/token-storage.worker.js b/npm/ng-packs/packages/oauth/src/lib/workers/token-storage.worker.js new file mode 100644 index 00000000000..bdf0e3bf170 --- /dev/null +++ b/npm/ng-packs/packages/oauth/src/lib/workers/token-storage.worker.js @@ -0,0 +1,52 @@ +// ESM SharedWorker + +const tokenStore = new Map(); +const ports = new Set(); + +self.onconnect = (event) => { + const port = event.ports[0]; + ports.add(port); + port.onmessage = (e) => { + const { action, key, value } = e.data; + switch (action) { + case 'set': + if (key && value !== undefined) { + tokenStore.set(key, value); + ports.forEach(p => { + if (p !== port) { + p.postMessage({ action: 'set', key, value }); + } + }); + } + break; + case 'remove': + if (key) { + tokenStore.delete(key); + ports.forEach(p => { + if (p !== port) { + p.postMessage({ action: 'remove', key }); + } + }); + } + break; + case 'clear': + tokenStore.clear(); + ports.forEach(p => { + if (p !== port) { + p.postMessage({ action: 'clear' }); + } + }); + break; + case 'get': + if (key) { + const value = tokenStore.get(key) ?? null; + port.postMessage({ action: 'get', key, value }); + } + break; + } + }; + + port.start(); +}; + +export {};