Browse Source

refactoring

pull/24050/head
erdemcaygor 4 months ago
parent
commit
cc196f95ca
  1. 4
      npm/ng-packs/packages/oauth/package.json
  2. 10
      npm/ng-packs/packages/oauth/project.json
  3. 163
      npm/ng-packs/packages/oauth/src/lib/services/memory-token-storage.service.ts
  4. 62
      npm/ng-packs/packages/oauth/src/lib/workers/token-storage.worker.js
  5. 98
      npm/ng-packs/packages/oauth/src/lib/workers/token-storage.worker.ts

4
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",

10
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",

163
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<string, string>();
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);
}
}

62
npm/ng-packs/packages/oauth/src/lib/workers/token-storage.worker.js

@ -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();
};

98
npm/ng-packs/packages/oauth/src/lib/workers/token-storage.worker.ts

@ -1,98 +0,0 @@
/// <reference lib="webworker" />
// 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<string, string>();
const ports = new Set<MessagePort>();
// 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<TokenMessage>) => {
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 {};
Loading…
Cancel
Save