mirror of https://github.com/OpenVidu/openvidu.git
ov-components: refactor(storage): Enhance tab management and cleanup mechanisms in StorageService
parent
b1d0269211
commit
e31a78d153
|
@ -19,7 +19,6 @@ import { TranslateService } from '../../services/translate/translate.service';
|
|||
import { LocalTrack } from 'livekit-client';
|
||||
import { CustomDevice } from '../../models/device.model';
|
||||
import { LangOption } from '../../models/lang.model';
|
||||
import { StorageService } from '../../services/storage/storage.service';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
@ -74,7 +73,6 @@ export class PreJoinComponent implements OnInit, OnDestroy {
|
|||
private libService: OpenViduComponentsConfigService,
|
||||
private cdkSrv: CdkOverlayService,
|
||||
private openviduService: OpenViduService,
|
||||
private storageService: StorageService,
|
||||
private translateService: TranslateService,
|
||||
private changeDetector: ChangeDetectorRef
|
||||
) {
|
||||
|
|
|
@ -9,7 +9,36 @@ export enum StorageKeys {
|
|||
CAMERA_ENABLED = 'cameraEnabled',
|
||||
LANG = 'lang',
|
||||
CAPTION_LANG = 'captionLang',
|
||||
BACKGROUND = "virtualBg"
|
||||
BACKGROUND = 'virtualBg',
|
||||
TAB_ID = 'tabId',
|
||||
ACTIVE_TABS = 'activeTabs'
|
||||
}
|
||||
|
||||
export const PERSISTENT_KEYS: StorageKeys[] = [
|
||||
StorageKeys.VIDEO_DEVICE,
|
||||
StorageKeys.AUDIO_DEVICE,
|
||||
StorageKeys.LANG,
|
||||
StorageKeys.CAPTION_LANG,
|
||||
StorageKeys.BACKGROUND
|
||||
];
|
||||
|
||||
export const SESSION_KEYS: StorageKeys[] = [StorageKeys.TAB_ID];
|
||||
|
||||
export const TAB_MANAGEMENT_KEYS: StorageKeys[] = [StorageKeys.TAB_ID, StorageKeys.ACTIVE_TABS];
|
||||
|
||||
// Data that should be unique per tab (stored in localStorage with tabId prefix)
|
||||
export const TAB_SPECIFIC_KEYS: StorageKeys[] = [
|
||||
StorageKeys.PARTICIPANT_NAME,
|
||||
StorageKeys.MICROPHONE_ENABLED,
|
||||
StorageKeys.CAMERA_ENABLED,
|
||||
StorageKeys.LANG,
|
||||
StorageKeys.CAPTION_LANG,
|
||||
StorageKeys.BACKGROUND,
|
||||
StorageKeys.VIDEO_DEVICE,
|
||||
StorageKeys.AUDIO_DEVICE
|
||||
];
|
||||
|
||||
// Data that should be truly persistent and shared between tabs
|
||||
export const SHARED_PERSISTENT_KEYS: StorageKeys[] = [];
|
||||
|
||||
export const STORAGE_PREFIX = 'ovComponents-';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
import { ILogger } from '../../models/logger.model';
|
||||
import { STORAGE_PREFIX, StorageKeys } from '../../models/storage.model';
|
||||
import { STORAGE_PREFIX, StorageKeys, SESSION_KEYS, TAB_MANAGEMENT_KEYS, TAB_SPECIFIC_KEYS, SHARED_PERSISTENT_KEYS } from '../../models/storage.model';
|
||||
import { LoggerService } from '../logger/logger.service';
|
||||
import { CustomDevice } from '../../models/device.model';
|
||||
|
||||
|
@ -10,13 +10,125 @@ import { CustomDevice } from '../../models/device.model';
|
|||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class StorageService {
|
||||
public storage = window.localStorage;
|
||||
export class StorageService implements OnDestroy {
|
||||
public localStorage = window.localStorage;
|
||||
public sessionStorage = window.sessionStorage;
|
||||
public log: ILogger;
|
||||
protected PREFIX_KEY = STORAGE_PREFIX;
|
||||
private tabId: string;
|
||||
private readonly TAB_CLEANUP_INTERVAL = 30000; // 30 seconds
|
||||
private cleanupInterval: any;
|
||||
|
||||
constructor(protected loggerSrv: LoggerService) {
|
||||
this.log = this.loggerSrv.get('StorageService');
|
||||
this.initializeTabManagement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes tab management system
|
||||
* Creates unique tab ID and sets up cleanup mechanism
|
||||
*/
|
||||
private initializeTabManagement(): void {
|
||||
// Generate unique tab ID
|
||||
this.tabId = this.generateTabId();
|
||||
this.setSessionValue(StorageKeys.TAB_ID, this.tabId);
|
||||
|
||||
// Register this tab as active
|
||||
this.registerActiveTab();
|
||||
|
||||
// Set up periodic cleanup of inactive tabs
|
||||
this.setupTabCleanup();
|
||||
|
||||
// Listen for page unload to clean up this tab
|
||||
window.addEventListener('beforeunload', () => {
|
||||
this.unregisterActiveTab();
|
||||
});
|
||||
|
||||
this.log.d(`Tab initialized with ID: ${this.tabId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique tab identifier
|
||||
*/
|
||||
private generateTabId(): string {
|
||||
return `tab_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers current tab as active
|
||||
*/
|
||||
private registerActiveTab(): void {
|
||||
const activeTabs = this.getActiveTabsFromStorage() || {};
|
||||
activeTabs[this.tabId] = Date.now();
|
||||
this.setLocalValue(StorageKeys.ACTIVE_TABS, activeTabs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters current tab from active tabs
|
||||
*/
|
||||
private unregisterActiveTab(): void {
|
||||
const activeTabs = this.getActiveTabsFromStorage() || {};
|
||||
delete activeTabs[this.tabId];
|
||||
this.setLocalValue(StorageKeys.ACTIVE_TABS, activeTabs);
|
||||
this.cleanupTabData(this.tabId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up periodic cleanup of inactive tabs
|
||||
*/
|
||||
private setupTabCleanup(): void {
|
||||
this.cleanupInterval = setInterval(() => {
|
||||
this.cleanupInactiveTabs();
|
||||
}, this.TAB_CLEANUP_INTERVAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up data from inactive tabs
|
||||
*/
|
||||
private cleanupInactiveTabs(): void {
|
||||
const activeTabs = this.getActiveTabsFromStorage() || {};
|
||||
const currentTime = Date.now();
|
||||
const timeoutThreshold = this.TAB_CLEANUP_INTERVAL * 2; // 60 seconds
|
||||
|
||||
Object.keys(activeTabs).forEach(tabId => {
|
||||
const lastActivity = activeTabs[tabId];
|
||||
if (currentTime - lastActivity > timeoutThreshold) {
|
||||
this.log.d(`Cleaning up inactive tab: ${tabId}`);
|
||||
delete activeTabs[tabId];
|
||||
this.cleanupTabData(tabId);
|
||||
}
|
||||
});
|
||||
|
||||
// Update heartbeat for current tab
|
||||
activeTabs[this.tabId] = currentTime;
|
||||
this.setLocalValue(StorageKeys.ACTIVE_TABS, activeTabs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up data associated with a specific tab
|
||||
*/
|
||||
private cleanupTabData(tabId: string): void {
|
||||
// Clean up tab-specific data from localStorage
|
||||
TAB_SPECIFIC_KEYS.forEach(key => {
|
||||
const storageKey = `${this.PREFIX_KEY}${tabId}_${key}`;
|
||||
this.localStorage.removeItem(storageKey);
|
||||
});
|
||||
|
||||
this.log.d(`Cleaned up data for tab: ${tabId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets active tabs from localStorage
|
||||
*/
|
||||
private getActiveTabsFromStorage(): { [key: string]: number } | null {
|
||||
return this.getLocalValue(StorageKeys.ACTIVE_TABS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current tab ID
|
||||
*/
|
||||
public getTabId(): string {
|
||||
return this.tabId;
|
||||
}
|
||||
|
||||
getParticipantName(): string | null {
|
||||
|
@ -106,24 +218,164 @@ export class StorageService {
|
|||
}
|
||||
|
||||
protected set(key: string, item: any) {
|
||||
const value = JSON.stringify({ item: item });
|
||||
this.storage.setItem(this.PREFIX_KEY + key, value);
|
||||
if (SESSION_KEYS.includes(key as StorageKeys)) {
|
||||
this.setSessionValue(key, item);
|
||||
} else {
|
||||
this.setLocalValue(key, item);
|
||||
}
|
||||
}
|
||||
|
||||
protected get(key: string): any {
|
||||
const str = this.storage.getItem(this.PREFIX_KEY + key);
|
||||
if (SESSION_KEYS.includes(key as StorageKeys)) {
|
||||
return this.getSessionValue(key);
|
||||
} else {
|
||||
return this.getLocalValue(key);
|
||||
}
|
||||
}
|
||||
|
||||
protected remove(key: string) {
|
||||
if (SESSION_KEYS.includes(key as StorageKeys)) {
|
||||
this.removeSessionValue(key);
|
||||
} else {
|
||||
this.removeLocalValue(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a key should use tab-specific storage in localStorage
|
||||
*/
|
||||
private shouldUseTabSpecificKey(key: string): boolean {
|
||||
return TAB_SPECIFIC_KEYS.includes(key as StorageKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets value in localStorage with tab-specific key if needed
|
||||
*/
|
||||
private setLocalValue(key: string, item: any): void {
|
||||
const value = JSON.stringify({ item: item });
|
||||
const storageKey = this.shouldUseTabSpecificKey(key)
|
||||
? `${this.PREFIX_KEY}${this.tabId}_${key}`
|
||||
: `${this.PREFIX_KEY}${key}`;
|
||||
this.localStorage.setItem(storageKey, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets value from localStorage with tab-specific key if needed
|
||||
*/
|
||||
private getLocalValue(key: string): any {
|
||||
const storageKey = this.shouldUseTabSpecificKey(key)
|
||||
? `${this.PREFIX_KEY}${this.tabId}_${key}`
|
||||
: `${this.PREFIX_KEY}${key}`;
|
||||
const str = this.localStorage.getItem(storageKey);
|
||||
if (!!str) {
|
||||
return JSON.parse(str).item;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected remove(key: string) {
|
||||
this.storage.removeItem(this.PREFIX_KEY + key);
|
||||
/**
|
||||
* Removes value from localStorage with tab-specific key if needed
|
||||
*/
|
||||
private removeLocalValue(key: string): void {
|
||||
const storageKey = this.shouldUseTabSpecificKey(key)
|
||||
? `${this.PREFIX_KEY}${this.tabId}_${key}`
|
||||
: `${this.PREFIX_KEY}${key}`;
|
||||
this.localStorage.removeItem(storageKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets value in sessionStorage
|
||||
*/
|
||||
private setSessionValue(key: string, item: any): void {
|
||||
const value = JSON.stringify({ item: item });
|
||||
this.sessionStorage.setItem(this.PREFIX_KEY + key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets value from sessionStorage
|
||||
*/
|
||||
private getSessionValue(key: string): any {
|
||||
const str = this.sessionStorage.getItem(this.PREFIX_KEY + key);
|
||||
if (!!str) {
|
||||
return JSON.parse(str).item;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes value from sessionStorage
|
||||
*/
|
||||
private removeSessionValue(key: string): void {
|
||||
this.sessionStorage.removeItem(this.PREFIX_KEY + key);
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this.log.d('Clearing localStorage');
|
||||
this.storage.clear();
|
||||
this.log.d('Clearing localStorage and sessionStorage');
|
||||
|
||||
// Clear only our prefixed keys from localStorage
|
||||
Object.keys(this.localStorage).forEach(key => {
|
||||
if (key.startsWith(this.PREFIX_KEY)) {
|
||||
this.localStorage.removeItem(key);
|
||||
}
|
||||
});
|
||||
|
||||
// Clear only our prefixed keys from sessionStorage
|
||||
Object.keys(this.sessionStorage).forEach(key => {
|
||||
if (key.startsWith(this.PREFIX_KEY)) {
|
||||
this.sessionStorage.removeItem(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears only session data (tab-specific data)
|
||||
*/
|
||||
public clearSessionData(): void {
|
||||
this.log.d('Clearing session data');
|
||||
Object.keys(this.sessionStorage).forEach(key => {
|
||||
if (key.startsWith(this.PREFIX_KEY)) {
|
||||
this.sessionStorage.removeItem(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears only tab-specific data for current tab
|
||||
*/
|
||||
public clearTabSpecificData(): void {
|
||||
this.log.d('Clearing tab-specific data');
|
||||
TAB_SPECIFIC_KEYS.forEach(key => {
|
||||
this.removeLocalValue(key);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears only persistent data
|
||||
*/
|
||||
public clearPersistentData(): void {
|
||||
this.log.d('Clearing persistent data');
|
||||
SHARED_PERSISTENT_KEYS.forEach(key => {
|
||||
this.removeLocalValue(key);
|
||||
});
|
||||
TAB_MANAGEMENT_KEYS.forEach(key => {
|
||||
this.removeLocalValue(key);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup method to be called when service is destroyed
|
||||
*/
|
||||
public destroy(): void {
|
||||
if (this.cleanupInterval) {
|
||||
clearInterval(this.cleanupInterval);
|
||||
}
|
||||
this.unregisterActiveTab();
|
||||
}
|
||||
|
||||
/**
|
||||
* Angular lifecycle hook - called when service is destroyed
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.destroy();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue