From cc196f95ca592c033461518da2cdf0bb2216e786 Mon Sep 17 00:00:00 2001 From: erdemcaygor Date: Mon, 22 Dec 2025 17:31:10 +0300 Subject: [PATCH] refactoring --- npm/ng-packs/packages/oauth/package.json | 4 - npm/ng-packs/packages/oauth/project.json | 10 +- .../services/memory-token-storage.service.ts | 163 ++++++++++++++++-- .../src/lib/workers/token-storage.worker.js | 62 ------- .../src/lib/workers/token-storage.worker.ts | 98 ----------- 5 files changed, 147 insertions(+), 190 deletions(-) delete mode 100644 npm/ng-packs/packages/oauth/src/lib/workers/token-storage.worker.js delete mode 100644 npm/ng-packs/packages/oauth/src/lib/workers/token-storage.worker.ts diff --git a/npm/ng-packs/packages/oauth/package.json b/npm/ng-packs/packages/oauth/package.json index 646fd7c73d..99c4a06485 100644 --- a/npm/ng-packs/packages/oauth/package.json +++ b/npm/ng-packs/packages/oauth/package.json @@ -6,10 +6,6 @@ "type": "git", "url": "https://github.com/abpframework/abp.git" }, - "scripts": { - "build:worker": "esbuild src/lib/workers/token-storage.worker.ts --bundle --format=esm --target=es2022 --outfile=src/lib/workers/token-storage.worker.js", - "watch:worker": "esbuild src/lib/workers/token-storage.worker.ts --bundle --format=esm --target=es2022 --outfile=src/lib/workers/token-storage.worker.js --watch" - }, "dependencies": { "@abp/ng.core": "~10.0.1", "@abp/utils": "~10.0.1", diff --git a/npm/ng-packs/packages/oauth/project.json b/npm/ng-packs/packages/oauth/project.json index 98c7cba04f..61662f4811 100644 --- a/npm/ng-packs/packages/oauth/project.json +++ b/npm/ng-packs/packages/oauth/project.json @@ -5,13 +5,6 @@ "sourceRoot": "packages/oauth/src", "prefix": "abp", "targets": { - "build-worker": { - "executor": "nx:run-commands", - "options": { - "command": "esbuild src/lib/workers/token-storage.worker.ts --bundle --format=esm --target=es2022 --outfile=src/lib/workers/token-storage.worker.js", - "cwd": "packages/oauth" - } - }, "build": { "executor": "@nx/angular:package", "outputs": ["{workspaceRoot}/dist/packages/oauth"], @@ -26,8 +19,7 @@ "tsConfig": "packages/oauth/tsconfig.lib.json" } }, - "defaultConfiguration": "production", - "dependsOn": ["build-worker"] + "defaultConfiguration": "production" }, "test": { "executor": "@nx/jest:jest", 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 index 0ded8e2c61..855f954975 100644 --- 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 @@ -1,16 +1,25 @@ -import { DOCUMENT, inject, Injectable } from '@angular/core'; +import { DestroyRef, DOCUMENT, inject, Injectable } from '@angular/core'; import { OAuthStorage } from 'angular-oauth2-oidc'; import { AbpLocalStorageService } from '@abp/ng.core'; +import { fromEvent } from 'rxjs'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Injectable({ providedIn: 'root', }) export class MemoryTokenStorageService implements OAuthStorage { + private static workerUrl: string | null = null; + 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' + '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; @@ -18,32 +27,36 @@ export class MemoryTokenStorageService implements OAuthStorage { private cache = new Map(); private localStorageService = inject(AbpLocalStorageService); private _document = inject(DOCUMENT); + private destroyRef = inject(DestroyRef); private useSharedWorker = false; constructor() { this.initializeStorage(); + this.setupCleanup(); } private initializeStorage(): void { - console.log("Initialize Storage -->>", typeof SharedWorker !== 'undefined'); + console.log('Initialize Storage -->>', typeof SharedWorker !== 'undefined'); // @ts-ignore if (typeof SharedWorker !== 'undefined') { try { console.log('Shared worker is loaded'); + console.log('refresh view'); + // Create worker from data URL to avoid path resolution issues in consuming apps + // Data URLs are deterministic - same content produces same URL across all tabs + if (!MemoryTokenStorageService.workerUrl) { + MemoryTokenStorageService.workerUrl = this.createWorkerDataUrl(); + } // @ts-ignore - this.worker = new SharedWorker( - new URL( - '../workers/token-storage.worker.js', - import.meta.url - ), - { name: 'oauth-token-storage', type: "module" } - ); - console.log("loaded worker -->>", this.worker); + this.worker = new SharedWorker(MemoryTokenStorageService.workerUrl, { + name: 'oauth-token-storage', + }); + console.log('loaded worker -->>', this.worker); this.port = this.worker.port; this.port.start(); this.useSharedWorker = true; - this.port.onmessage = (event) => { + this.port.onmessage = event => { const { action, key, value } = event.data; switch (action) { @@ -122,14 +135,130 @@ export class MemoryTokenStorageService implements OAuthStorage { this.cache.clear(); } + private setupCleanup(): void { + const cleanup = () => { + if (this.useSharedWorker && this.port) { + try { + this.port.postMessage({ action: 'disconnect' }); + } catch (error) { + console.log('Error disconnecting port:', error); + } + } + }; + + if (this._document.defaultView) { + fromEvent(this._document.defaultView, 'beforeunload') + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(() => cleanup()); + + fromEvent(this._document.defaultView, 'pagehide') + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(() => cleanup()); + } + } + private checkAuthStateChanges = (key: string) => { if (key === 'access_token' && !this.cache.get('access_token')) { this.refreshDocument(); } - } + }; private refreshDocument(): void { - this._document.defaultView?.location.reload(); + if (this.useSharedWorker && this.port) { + try { + this.port.postMessage({ action: 'disconnect' }); + } catch (error) { + console.log('Error sending disconnect before reload:', error); + } + } + setTimeout(() => { + this._document.defaultView?.location.reload(); + }, 100); + } + + private createWorkerDataUrl(): string { + const workerScript = `const tokenStore = new Map(); +const ports = new Set(); + +function broadcastToOtherPorts(senderPort, message) { + const deadPorts = []; + ports.forEach(p => { + if (p !== senderPort) { + try { + p.postMessage(message); + } catch (error) { + console.log('Dead port detected during broadcast, removing...'); + deadPorts.push(p); + } + } + }); + deadPorts.forEach(p => ports.delete(p)); + if (deadPorts.length > 0) { + console.log('Cleaned up', deadPorts.length, 'dead ports. Total ports:', ports.size); } +} + +function removePort(port) { + if (ports.has(port)) { + ports.delete(port); + console.log('Port disconnected. Total ports:', ports.size); + } +} + +self.onconnect = (event) => { + const port = event.ports[0]; + ports.add(port); + console.log('Port connected. Total ports:', ports.size); + + port.addEventListener('messageerror', () => { + removePort(port); + }); + port.onmessage = (e) => { + const { action, key, value } = e.data; + + switch (action) { + case 'set': + if (key && value !== undefined) { + tokenStore.set(key, value); + broadcastToOtherPorts(port, { action: 'set', key, value }); + } + break; + + case 'remove': + if (key) { + console.log('remove', key); + tokenStore.delete(key); + broadcastToOtherPorts(port, { action: 'remove', key }); + } + break; + + case 'clear': + console.log('clear user'); + tokenStore.clear(); + broadcastToOtherPorts(port, { action: 'clear' }); + break; + + case 'get': + console.log('get user'); + if (key) { + const value = tokenStore.get(key) ?? null; + port.postMessage({ action: 'get', key, value }); + } + break; + + case 'disconnect': + console.log('Port requested disconnect'); + removePort(port); + break; + + default: + console.warn('Unknown action:', action); + } + }; + + port.start(); +};`; + return 'data:application/javascript;base64,' + btoa(workerScript); + } } 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 deleted file mode 100644 index b4032ce09a..0000000000 --- a/npm/ng-packs/packages/oauth/src/lib/workers/token-storage.worker.js +++ /dev/null @@ -1,62 +0,0 @@ -// src/lib/workers/token-storage.worker.ts -var tokenStore = /* @__PURE__ */ new Map(); -var ports = /* @__PURE__ */ new Set(); -function broadcastToOtherPorts(senderPort, message) { - const deadPorts = []; - ports.forEach((p) => { - if (p !== senderPort) { - try { - p.postMessage(message); - } catch (error) { - console.log("Dead port detected, removing..."); - deadPorts.push(p); - } - } - }); - deadPorts.forEach((p) => ports.delete(p)); - if (deadPorts.length > 0) { - console.log("Cleaned up dead ports. Total ports:", ports.size); - } -} -self.onconnect = (event) => { - const port = event.ports[0]; - ports.add(port); - console.log("Port connected. Total ports:", ports.size); - port.addEventListener("messageerror", () => { - ports.delete(port); - console.log("Port disconnected (error). Total ports:", ports.size); - }); - port.onmessage = (e) => { - const { action, key, value } = e.data; - switch (action) { - case "set": - if (key && value !== void 0) { - tokenStore.set(key, value); - broadcastToOtherPorts(port, { action: "set", key, value }); - } - break; - case "remove": - if (key) { - console.log("remove", key); - tokenStore.delete(key); - broadcastToOtherPorts(port, { action: "remove", key }); - } - break; - case "clear": - console.log("clear user"); - tokenStore.clear(); - broadcastToOtherPorts(port, { action: "clear" }); - break; - case "get": - console.log("get user"); - if (key) { - const value2 = tokenStore.get(key) ?? null; - port.postMessage({ action: "get", key, value: value2 }); - } - break; - default: - console.warn("Unknown action:", action); - } - }; - port.start(); -}; diff --git a/npm/ng-packs/packages/oauth/src/lib/workers/token-storage.worker.ts b/npm/ng-packs/packages/oauth/src/lib/workers/token-storage.worker.ts deleted file mode 100644 index a5c422738a..0000000000 --- a/npm/ng-packs/packages/oauth/src/lib/workers/token-storage.worker.ts +++ /dev/null @@ -1,98 +0,0 @@ -/// - -// Message types for type safety -interface TokenMessage { - action: 'set' | 'remove' | 'clear' | 'get'; - key?: string; - value?: string; -} - -interface TokenResponse { - action: 'set' | 'remove' | 'clear' | 'get'; - key?: string; - value?: string | null; -} - -declare const self: SharedWorkerGlobalScope; - -const tokenStore = new Map(); -const ports = new Set(); - -// Broadcast message to all ports except the sender -// Automatically cleans up dead ports -function broadcastToOtherPorts(senderPort: MessagePort, message: TokenResponse): void { - const deadPorts: MessagePort[] = []; - - ports.forEach(p => { - if (p !== senderPort) { - try { - p.postMessage(message); - } catch (error) { - // Port is dead/closed, mark for removal - console.log('Dead port detected, removing...'); - deadPorts.push(p); - } - } - }); - - // Clean up dead ports - deadPorts.forEach(p => ports.delete(p)); - - if (deadPorts.length > 0) { - console.log('Cleaned up dead ports. Total ports:', ports.size); - } -} - -self.onconnect = (event: MessageEvent) => { - const port = event.ports[0]; - ports.add(port); - console.log('Port connected. Total ports:', ports.size); - - // Clean up port when it's closed - port.addEventListener('messageerror', () => { - ports.delete(port); - console.log('Port disconnected (error). Total ports:', ports.size); - }); - - port.onmessage = (e: MessageEvent) => { - const { action, key, value } = e.data; - - switch (action) { - case 'set': - if (key && value !== undefined) { - tokenStore.set(key, value); - broadcastToOtherPorts(port, { action: 'set', key, value }); - } - break; - - case 'remove': - if (key) { - console.log('remove', key); - tokenStore.delete(key); - broadcastToOtherPorts(port, { action: 'remove', key }); - } - break; - - case 'clear': - console.log('clear user'); - tokenStore.clear(); - broadcastToOtherPorts(port, { action: 'clear' }); - break; - - case 'get': - console.log('get user'); - if (key) { - const value = tokenStore.get(key) ?? null; - port.postMessage({ action: 'get', key, value } as TokenResponse); - } - break; - - default: - console.warn('Unknown action:', action); - } - }; - - port.start(); -}; - -export {};