Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions npm/ng-packs/packages/oauth/src/lib/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private worker?: SharedWorker;
It would be better to add a proper type for worker instead of leaving it as an optional SharedWorker. This improves clarity and type safety.

private port?: MessagePort;
private cache = new Map<string, string>();
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(
Comment on lines +28 to +32
Copy link

Copilot AI Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace @ts-ignore comments with proper TypeScript type declarations or type guards. The repeated use of @ts-ignore suppresses type checking and can hide potential runtime errors.

Copilot uses AI. Check for mistakes.
new URL('../workers/token-storage.worker.ts', import.meta.url),
{ name: 'oauth-token-storage' }
);

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;
}
};

console.log('✅ Using SharedWorker for cross-tab token storage');
Copy link

Copilot AI Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove or replace console.log statements in production code. Consider using a proper logging service or removing this debug statement entirely.

Suggested change
console.log('✅ Using SharedWorker for cross-tab token storage');

Copilot uses AI. Check for mistakes.
} catch (error) {
console.warn('⚠️ SharedWorker failed:', error);
Copy link

Copilot AI Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a proper logging service instead of console.warn for error reporting in production code. This would allow better error tracking and debugging capabilities.

Copilot uses AI. Check for mistakes.
this.useSharedWorker = false;
}
} else {
console.warn('⚠️ SharedWorker not supported');
Copy link

Copilot AI Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a proper logging service instead of console.warn. This would provide consistent logging throughout the application.

Copilot uses AI. Check for mistakes.
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);
Copy link

Copilot AI Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove commented-out code. If this line is needed, it should be uncommented and executed; otherwise, it should be deleted entirely.

Suggested change
// this.cache.delete(key);

Copilot uses AI. Check for mistakes.
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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,11 @@ 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) {
return Promise.resolve();
Copy link

Copilot AI Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The catch block silently swallows errors without logging or providing any feedback. Consider logging the error or returning a rejected promise with an appropriate error message to help with debugging authentication issues.

Suggested change
return Promise.resolve();
console.error('Error during token refresh:', error);
return Promise.reject(error);

Copilot uses AI. Check for mistakes.
}
}

getAccessTokenExpiration(): number {
Expand Down
6 changes: 3 additions & 3 deletions npm/ng-packs/packages/oauth/src/lib/utils/storage.factory.ts
Original file line number Diff line number Diff line change
@@ -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<string, string>();
Expand Down Expand Up @@ -35,5 +35,5 @@ export function oAuthStorageFactory(): OAuthStorage {
? inject(BrowserTokenStorageService)
: inject(ServerTokenStorageService);
}
return inject(AbpLocalStorageService);
return inject(MemoryTokenStorageService);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/// <reference no-default-lib="true"/>
/// <reference lib="es2020" />
/// <reference lib="webworker" />

declare const self: SharedWorkerGlobalScope;

interface TokenMessage {
action: 'set' | 'remove' | 'clear' | 'get';
key?: string;
value?: string;
}

const tokenStore = new Map<string, string>();
const ports = new Set<MessagePort>();

self.onconnect = (event: MessageEvent) => {
const port = event.ports[0];
ports.add(port);

port.onmessage = (e: MessageEvent<TokenMessage>) => {
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 {};
Loading