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 { SpotifyTokenService } from "./spotifyTokenService";
import logger from "../utils/logger"; import logger from "../utils/logger";
const userStateCache = new Map<string, any>();
const activePolls = new Map<string, NodeJS.Timeout>();
export class SpotifyPollingService { export class SpotifyPollingService {
private readonly userStateCache = new Map<string, any>();
private readonly activePolls = new Map<string, NodeJS.Timeout>();
constructor( constructor(
private readonly userService: UserService, private readonly userService: UserService,
private readonly spotifyApiService: SpotifyApiService, private readonly spotifyApiService: SpotifyApiService,
@@ -18,21 +20,33 @@ export class SpotifyPollingService {
public startPollingForUser(user: IUser): void { public startPollingForUser(user: IUser): void {
const uuid = user.uuid; const uuid = user.uuid;
if (activePolls.has(uuid)) return; if (this.activePolls.has(uuid)) return;
logger.info(`Starting Spotify polling service for user ${uuid}`); 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 { public stopPollingForUser(uuid: string): void {
if (activePolls.has(uuid)) { if (this.activePolls.has(uuid)) {
logger.info(`Stopping Spotify polling service for user ${uuid}`); logger.info(`Stopping Spotify polling service for user ${uuid}`);
clearInterval(activePolls.get(uuid)!); clearInterval(this.activePolls.get(uuid)!);
activePolls.delete(uuid); this.activePolls.delete(uuid);
userStateCache.delete(uuid); this.userStateCache.delete(uuid);
} }
} }
@@ -59,11 +73,11 @@ export class SpotifyPollingService {
} }
const currentState = await this.spotifyApiService.getCurrentlyPlaying(user!.spotifyConfig!.accessToken); 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)) { if (this._hasStateChanged(lastState, currentState)) {
logger.debug(`Spotify state changed for user ${uuid} - emitting update event`); 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 }); appEventBus.emit(SPOTIFY_STATE_UPDATED_EVENT, { uuid, state: currentState });
} }
} catch (error) { } catch (error) {
@@ -90,9 +104,9 @@ export class SpotifyPollingService {
} }
private _pausePolling(uuid: string, durationMs: number): void { private _pausePolling(uuid: string, durationMs: number): void {
if (activePolls.has(uuid)) { if (this.activePolls.has(uuid)) {
clearInterval(activePolls.get(uuid)!); clearInterval(this.activePolls.get(uuid)!);
activePolls.delete(uuid); this.activePolls.delete(uuid);
setTimeout(() => { setTimeout(() => {
logger.debug(`Resuming Spotify polling service for user ${uuid}`); logger.debug(`Resuming Spotify polling service for user ${uuid}`);
this.userService.getUserByUUID(uuid).then((user) => { this.userService.getUserByUUID(uuid).then((user) => {
+9 -7
View File
@@ -34,8 +34,7 @@ describe("SpotifyPollingService", () => {
}, },
} as any; } as any;
beforeEach(async () => { beforeEach(() => {
vi.resetModules();
vi.clearAllMocks(); vi.clearAllMocks();
vi.useFakeTimers(); vi.useFakeTimers();
@@ -45,9 +44,7 @@ describe("SpotifyPollingService", () => {
mockedTokenService = createMockSpotifyTokenService() as any; mockedTokenService = createMockSpotifyTokenService() as any;
mockedAppEventBus = appEventBus as Mocked<typeof appEventBus>; mockedAppEventBus = appEventBus as Mocked<typeof appEventBus>;
const { SpotifyPollingService: FreshSpotifyPollingService } = await import('../../src/services/spotifyPollingService'); pollingService = new SpotifyPollingService(
pollingService = new FreshSpotifyPollingService(
mockedUserService, mockedUserService,
mockedApiService, mockedApiService,
mockedTokenService mockedTokenService
@@ -57,6 +54,11 @@ describe("SpotifyPollingService", () => {
}); });
afterEach(() => { afterEach(() => {
if (pollingService) {
pollingService.stopPollingForUser(mockUser.uuid);
}
vi.clearAllTimers();
vi.useRealTimers(); vi.useRealTimers();
}); });
@@ -97,7 +99,7 @@ describe("SpotifyPollingService", () => {
expect(vi.getTimerCount()).toBe(1); expect(vi.getTimerCount()).toBe(1);
pollingService.stopPollingForUser(mockUser.uuid); 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); await vi.advanceTimersByTimeAsync(5000);
expect(mockedApiService.getCurrentlyPlaying).toHaveBeenCalledTimes(2); expect(mockedApiService.getCurrentlyPlaying).toHaveBeenCalledTimes(2);
expect(vi.getTimerCount()).toBe(1); expect(pollingService.activePolls.size).toBe(1);
}); });
}); });
}); });