refactor: Improve SpotifyPollingService by encapsulating state management and simplifying polling logic

This commit is contained in:
StarAppeal
2025-09-29 06:15:41 +02:00
parent 5fc7174787
commit 11680b4cc3
2 changed files with 38 additions and 22 deletions
+29 -15
View File
@@ -6,10 +6,12 @@ import { UserService } from "./db/UserService";
import { SpotifyTokenService } from "./spotifyTokenService";
import logger from "../utils/logger";
const userStateCache = new Map<string, any>();
const activePolls = new Map<string, NodeJS.Timeout>();
export class SpotifyPollingService {
private readonly userStateCache = new Map<string, any>();
private readonly activePolls = new Map<string, NodeJS.Timeout>();
constructor(
private readonly userService: UserService,
private readonly spotifyApiService: SpotifyApiService,
@@ -18,21 +20,33 @@ export class SpotifyPollingService {
public startPollingForUser(user: IUser): void {
const uuid = user.uuid;
if (activePolls.has(uuid)) return;
if (this.activePolls.has(uuid)) return;
logger.info(`Starting Spotify polling service for user ${uuid}`);
const intervalId = setInterval(() => this._pollUser(uuid), 3000);
activePolls.set(uuid, intervalId);
this._pollUser(uuid);
const poll = async () => {
if (!this.activePolls.has(uuid)) return;
await this._pollUser(uuid);
if (this.activePolls.has(uuid)) {
const timeoutId = setTimeout(poll, 3000);
this.activePolls.set(uuid, timeoutId);
}
};
this.activePolls.set(uuid, null as any);
poll();
}
public stopPollingForUser(uuid: string): void {
if (activePolls.has(uuid)) {
if (this.activePolls.has(uuid)) {
logger.info(`Stopping Spotify polling service for user ${uuid}`);
clearInterval(activePolls.get(uuid)!);
activePolls.delete(uuid);
userStateCache.delete(uuid);
clearInterval(this.activePolls.get(uuid)!);
this.activePolls.delete(uuid);
this.userStateCache.delete(uuid);
}
}
@@ -59,11 +73,11 @@ export class SpotifyPollingService {
}
const currentState = await this.spotifyApiService.getCurrentlyPlaying(user!.spotifyConfig!.accessToken);
const lastState = userStateCache.get(uuid);
const lastState = this.userStateCache.get(uuid);
if (this._hasStateChanged(lastState, currentState)) {
logger.debug(`Spotify state changed for user ${uuid} - emitting update event`);
userStateCache.set(uuid, currentState);
this.userStateCache.set(uuid, currentState);
appEventBus.emit(SPOTIFY_STATE_UPDATED_EVENT, { uuid, state: currentState });
}
} catch (error) {
@@ -90,9 +104,9 @@ export class SpotifyPollingService {
}
private _pausePolling(uuid: string, durationMs: number): void {
if (activePolls.has(uuid)) {
clearInterval(activePolls.get(uuid)!);
activePolls.delete(uuid);
if (this.activePolls.has(uuid)) {
clearInterval(this.activePolls.get(uuid)!);
this.activePolls.delete(uuid);
setTimeout(() => {
logger.debug(`Resuming Spotify polling service for user ${uuid}`);
this.userService.getUserByUUID(uuid).then((user) => {
+9 -7
View File
@@ -34,8 +34,7 @@ describe("SpotifyPollingService", () => {
},
} as any;
beforeEach(async () => {
vi.resetModules();
beforeEach(() => {
vi.clearAllMocks();
vi.useFakeTimers();
@@ -45,9 +44,7 @@ describe("SpotifyPollingService", () => {
mockedTokenService = createMockSpotifyTokenService() as any;
mockedAppEventBus = appEventBus as Mocked<typeof appEventBus>;
const { SpotifyPollingService: FreshSpotifyPollingService } = await import('../../src/services/spotifyPollingService');
pollingService = new FreshSpotifyPollingService(
pollingService = new SpotifyPollingService(
mockedUserService,
mockedApiService,
mockedTokenService
@@ -57,6 +54,11 @@ describe("SpotifyPollingService", () => {
});
afterEach(() => {
if (pollingService) {
pollingService.stopPollingForUser(mockUser.uuid);
}
vi.clearAllTimers();
vi.useRealTimers();
});
@@ -97,7 +99,7 @@ describe("SpotifyPollingService", () => {
expect(vi.getTimerCount()).toBe(1);
pollingService.stopPollingForUser(mockUser.uuid);
expect(vi.getTimerCount()).toBe(0);
expect(pollingService.activePolls.size).toBe(0);
});
});
@@ -190,7 +192,7 @@ describe("SpotifyPollingService", () => {
await vi.advanceTimersByTimeAsync(5000);
expect(mockedApiService.getCurrentlyPlaying).toHaveBeenCalledTimes(2);
expect(vi.getTimerCount()).toBe(1);
expect(pollingService.activePolls.size).toBe(1);
});
});
});