From 61ef8e2553a2a2e6e25fdf8fdd840dadfef31161 Mon Sep 17 00:00:00 2001 From: 4gray Date: Mon, 16 Feb 2026 23:37:05 +0100 Subject: [PATCH] refactor(xtream): introduce silent actions and improve error handling for Xtream requests --- apps/web/src/app/services/electron.service.ts | 74 +++++++++++--- apps/web/src/app/services/pwa.service.ts | 98 ++++++++++++++++--- .../services/xtream-api.service.ts | 2 +- .../interfaces/src/lib/xtream-code-actions.ts | 1 + 4 files changed, 151 insertions(+), 24 deletions(-) diff --git a/apps/web/src/app/services/electron.service.ts b/apps/web/src/app/services/electron.service.ts index 1b1abde7d..bc1db5763 100644 --- a/apps/web/src/app/services/electron.service.ts +++ b/apps/web/src/app/services/electron.service.ts @@ -11,6 +11,7 @@ import { Playlist, PLAYLIST_PARSE_BY_URL, PLAYLIST_UPDATE, + XtreamCodeActions, XTREAM_RESPONSE, XTREAM_REQUEST, } from 'shared-interfaces'; @@ -24,6 +25,12 @@ export class ElectronService extends DataService { private readonly snackBar = inject(MatSnackBar); private readonly store = inject(Store); private readonly translateService = inject(TranslateService); + private readonly silentXtreamActions = new Set([ + XtreamCodeActions.GetAccountInfo, + XtreamCodeActions.GetLiveCategories, + XtreamCodeActions.GetVodCategories, + XtreamCodeActions.GetSeriesCategories, + ]); constructor() { super(); @@ -263,24 +270,26 @@ export class ElectronService extends DataService { window.postMessage(result); return result; } catch (error: any) { - const isStatusCheck = payload.params?.action === 'get_account_info'; + const action = payload.params?.action; + const isSilentAction = action + ? this.silentXtreamActions.has(action) + : false; + const normalizedMessage = this.getReadableXtreamErrorMessage(error); // Log error to console - if (isStatusCheck) { + if (isSilentAction) { console.log( - 'Portal status check failed - portal may be unavailable:', - error.message || error + `Background Xtream action failed (${action ?? 'unknown'}):`, + normalizedMessage ); } else { - console.error('Xtream request error:', error.message); + console.error('Xtream request error:', normalizedMessage); } - // Only show snackbar for non-status-check requests - if (!isStatusCheck) { + // Only show snackbar for user-triggered Xtream requests + if (!isSilentAction) { this.snackBar.open( - `Error: ${error.message ?? 'Failed to connect to Xtream server'}, status: ${ - error.status ?? 500 - }`, + `Xtream request failed: ${normalizedMessage}`, 'Close', { duration: 5000, @@ -291,11 +300,54 @@ export class ElectronService extends DataService { return { type: ERROR, status: error.status ?? 500, - message: error.message ?? 'Failed to connect to Xtream server', + message: normalizedMessage, }; } } + private getReadableXtreamErrorMessage(error: unknown): string { + const fallback = 'Failed to connect to Xtream server'; + if (!error) { + return fallback; + } + + const maybeError = error as { + message?: unknown; + statusText?: unknown; + status?: unknown; + error?: unknown; + }; + + if (typeof maybeError.message === 'string') { + if (maybeError.message.includes('[object Object]')) { + if (typeof maybeError.error === 'string') { + return maybeError.error; + } + if ( + maybeError.error && + typeof maybeError.error === 'object' && + 'message' in (maybeError.error as Record) && + typeof (maybeError.error as Record).message === + 'string' + ) { + return (maybeError.error as Record).message; + } + return fallback; + } + return maybeError.message; + } + + if (typeof maybeError.statusText === 'string') { + return maybeError.statusText; + } + + if (typeof error === 'string') { + return error; + } + + return fallback; + } + removeAllListeners(type: string): void { if (type === 'all') { // Unsubscribe from all event listeners diff --git a/apps/web/src/app/services/pwa.service.ts b/apps/web/src/app/services/pwa.service.ts index 3f4c4d040..dce3faf15 100644 --- a/apps/web/src/app/services/pwa.service.ts +++ b/apps/web/src/app/services/pwa.service.ts @@ -14,6 +14,7 @@ import { PLAYLIST_PARSE_BY_URL, PLAYLIST_UPDATE, STALKER_REQUEST, + XtreamCodeActions, XTREAM_REQUEST, XTREAM_RESPONSE, } from 'shared-interfaces'; @@ -28,6 +29,12 @@ export class PwaService extends DataService { private readonly store = inject(Store); private readonly swUpdate = inject(SwUpdate); private readonly translateService = inject(TranslateService); + private readonly silentXtreamActions = new Set([ + XtreamCodeActions.GetAccountInfo, + XtreamCodeActions.GetLiveCategories, + XtreamCodeActions.GetVodCategories, + XtreamCodeActions.GetSeriesCategories, + ]); /** Proxy URL to avoid CORS issues */ corsProxyUrl = AppConfig.BACKEND_URL; @@ -186,15 +193,28 @@ export class PwaService extends DataService { ); if (!(response as any).payload) { - if (payload.params.action === 'get_account_info') { - console.log('Portal status check failed - portal may be unavailable:', (response as any).message || 'No payload received'); - return; + const action = payload.params.action; + const isSilentAction = this.silentXtreamActions.has(action); + const normalizedMessage = this.getReadableXtreamErrorMessage( + response + ); + + if (isSilentAction) { + console.log( + `Background Xtream action failed (${action ?? 'unknown'}):`, + normalizedMessage + ); + return { + type: ERROR, + status: (response as any).status ?? 500, + message: normalizedMessage, + }; } result = { type: ERROR, status: (response as any).status, - message: (response as any).message ?? 'Unknown error', + message: normalizedMessage, }; window.postMessage(result); } else { @@ -207,27 +227,81 @@ export class PwaService extends DataService { } return result; } catch (error: any) { - const isStatusCheck = payload.params.action === 'get_account_info'; + const action = payload.params.action; + const isSilentAction = this.silentXtreamActions.has(action); + const normalizedMessage = this.getReadableXtreamErrorMessage(error); // Log error to console - if (isStatusCheck) { - console.log('Portal status check failed - portal may be unavailable:', error.message || error); - return; + if (isSilentAction) { + console.log( + `Background Xtream action failed (${action ?? 'unknown'}):`, + normalizedMessage + ); + return { + type: ERROR, + status: error.status ?? 500, + message: normalizedMessage, + }; } - console.error('Xtream request error:', error.message); + console.error('Xtream request error:', normalizedMessage); this.snackBar.open( - `Error: ${error.message ?? ' Unknown error'}, status: ${ - error.status ?? 500 - }`, + `Xtream request failed: ${normalizedMessage}`, 'Close', { duration: 5000, } ); + return { + type: ERROR, + status: error.status ?? 500, + message: normalizedMessage, + }; } } + private getReadableXtreamErrorMessage(error: unknown): string { + const fallback = 'Failed to connect to Xtream server'; + if (!error) { + return fallback; + } + + const maybeError = error as { + message?: unknown; + statusText?: unknown; + error?: unknown; + }; + + if (typeof maybeError.message === 'string') { + if (maybeError.message.includes('[object Object]')) { + if (typeof maybeError.error === 'string') { + return maybeError.error; + } + if ( + maybeError.error && + typeof maybeError.error === 'object' && + 'message' in (maybeError.error as Record) && + typeof (maybeError.error as Record).message === + 'string' + ) { + return (maybeError.error as Record).message; + } + return fallback; + } + return maybeError.message; + } + + if (typeof maybeError.statusText === 'string') { + return maybeError.statusText; + } + + if (typeof error === 'string') { + return error; + } + + return fallback; + } + async forwardStalkerRequest(payload: { url: string; params: Record; diff --git a/apps/web/src/app/xtream-tauri/services/xtream-api.service.ts b/apps/web/src/app/xtream-tauri/services/xtream-api.service.ts index d50246601..a2bfe6ca4 100644 --- a/apps/web/src/app/xtream-tauri/services/xtream-api.service.ts +++ b/apps/web/src/app/xtream-tauri/services/xtream-api.service.ts @@ -71,7 +71,7 @@ export class XtreamApiService { { username: credentials.username, password: credentials.password, - action: 'get_account_info', + action: XtreamCodeActions.GetAccountInfo, } ); } diff --git a/libs/shared/interfaces/src/lib/xtream-code-actions.ts b/libs/shared/interfaces/src/lib/xtream-code-actions.ts index 2f1b1828f..06d08a15f 100644 --- a/libs/shared/interfaces/src/lib/xtream-code-actions.ts +++ b/libs/shared/interfaces/src/lib/xtream-code-actions.ts @@ -1,4 +1,5 @@ export enum XtreamCodeActions { + GetAccountInfo = 'get_account_info', GetLiveStreams = 'get_live_streams', GetLiveCategories = 'get_live_categories', GetSeries = 'get_series',