diff --git a/apps/web/project.json b/apps/web/project.json index 6e4871dcd..548533c67 100644 --- a/apps/web/project.json +++ b/apps/web/project.json @@ -22,6 +22,7 @@ "polyfills": ["zone.js"], "tsConfig": "apps/web/tsconfig.app.json", "inlineStyleLanguage": "scss", + "serviceWorker": "ngsw-config.json", "assets": [ "apps/web/src/favicon.ico", "apps/web/src/assets", @@ -99,12 +100,14 @@ ] }, "development": { + "serviceWorker": false, "optimization": false, "extractLicenses": false, "sourceMap": true }, "electron-e2e": { "baseHref": "./", + "serviceWorker": false, "optimization": false, "extractLicenses": false, "sourceMap": true @@ -166,9 +169,9 @@ "continuous": true, "executor": "@nx/web:file-server", "options": { - "buildTarget": "web:build", + "buildTarget": "web:build:pwa", "port": 4200, - "staticFilePath": "dist/apps/web/browser", + "staticFilePath": "dist/apps/web", "spa": true } } diff --git a/apps/web/src/app/app.config.ts b/apps/web/src/app/app.config.ts index eafb9250f..70bb84b3f 100644 --- a/apps/web/src/app/app.config.ts +++ b/apps/web/src/app/app.config.ts @@ -40,6 +40,7 @@ import { } from './services/portal-navigation-actions.service'; import { providePortalPlaybackPositions } from './services/portal-playback-positions.service'; import { PwaService } from './services/pwa.service'; +import { shouldEnableServiceWorker } from './services/runtime-config'; import { provideWorkspaceShellActions } from './services/workspace-shell-actions.service'; // AoT requires an exported function for factories @@ -113,7 +114,7 @@ export const appConfig: ApplicationConfig = { provideRouterStore(), ...(AppConfig.production ? [] : [provideStoreDevtools({ maxAge: 25 })]), provideServiceWorker('ngsw-worker.js', { - enabled: AppConfig.production && !!window.electron, + enabled: shouldEnableServiceWorker(), registrationStrategy: 'registerWhenStable:30000', }), importProvidersFrom( diff --git a/apps/web/src/app/services/pwa.service.ts b/apps/web/src/app/services/pwa.service.ts index e231cdba6..18f74b762 100644 --- a/apps/web/src/app/services/pwa.service.ts +++ b/apps/web/src/app/services/pwa.service.ts @@ -25,6 +25,7 @@ import { logPortalDebugEvent, logPortalDebugRequest, } from '@iptvnator/portal/shared/util'; +import { getRuntimeBackendUrl } from './runtime-config'; interface PwaXtreamResponse { readonly payload?: unknown; @@ -69,7 +70,7 @@ export class PwaService extends DataService { ]); /** Proxy URL to avoid CORS issues */ - corsProxyUrl = AppConfig.BACKEND_URL; + corsProxyUrl = getRuntimeBackendUrl(); constructor() { super(); diff --git a/apps/web/src/app/services/runtime-config.spec.ts b/apps/web/src/app/services/runtime-config.spec.ts new file mode 100644 index 000000000..3efffcf35 --- /dev/null +++ b/apps/web/src/app/services/runtime-config.spec.ts @@ -0,0 +1,39 @@ +import { resolveBackendUrl, shouldEnableServiceWorker } from './runtime-config'; + +describe('runtime config helpers', () => { + it('uses runtime BACKEND_URL when provided', () => { + expect( + resolveBackendUrl( + { BACKEND_URL: ' http://self-hosted.local/api ' }, + 'https://fallback.example' + ) + ).toBe('http://self-hosted.local/api'); + }); + + it('falls back to the build-time backend URL when runtime config is missing or blank', () => { + expect(resolveBackendUrl(undefined, 'https://fallback.example')).toBe( + 'https://fallback.example' + ); + expect(resolveBackendUrl({}, 'https://fallback.example')).toBe( + 'https://fallback.example' + ); + expect( + resolveBackendUrl( + { BACKEND_URL: ' ' }, + 'https://fallback.example' + ) + ).toBe('https://fallback.example'); + }); + + it('enables service worker only for production builds with browser support', () => { + expect( + shouldEnableServiceWorker(true, { + serviceWorker: {}, + } as Navigator) + ).toBe(true); + expect( + shouldEnableServiceWorker(false, { serviceWorker: {} } as Navigator) + ).toBe(false); + expect(shouldEnableServiceWorker(true, {} as Navigator)).toBe(false); + }); +}); diff --git a/apps/web/src/app/services/runtime-config.ts b/apps/web/src/app/services/runtime-config.ts new file mode 100644 index 000000000..2a542a9b0 --- /dev/null +++ b/apps/web/src/app/services/runtime-config.ts @@ -0,0 +1,27 @@ +import { AppConfig } from '../../environments/environment'; + +export interface IptvnatorRuntimeConfig { + readonly BACKEND_URL?: string; +} + +export function resolveBackendUrl( + runtimeConfig: IptvnatorRuntimeConfig | undefined, + fallbackUrl: string +): string { + const runtimeUrl = runtimeConfig?.BACKEND_URL?.trim(); + return runtimeUrl || fallbackUrl; +} + +export function getRuntimeBackendUrl(): string { + return resolveBackendUrl( + globalThis.window?.__IPTVNATOR_CONFIG__, + AppConfig.BACKEND_URL + ); +} + +export function shouldEnableServiceWorker( + production = AppConfig.production, + navigatorRef: Navigator | undefined = globalThis.navigator +): boolean { + return production && !!navigatorRef && 'serviceWorker' in navigatorRef; +} diff --git a/apps/web/src/assets/app-config.js b/apps/web/src/assets/app-config.js new file mode 100644 index 000000000..bc466029f --- /dev/null +++ b/apps/web/src/assets/app-config.js @@ -0,0 +1 @@ +window.__IPTVNATOR_CONFIG__ = window.__IPTVNATOR_CONFIG__ || {}; diff --git a/apps/web/src/index.html b/apps/web/src/index.html index ef7b41fe9..21cd9eeea 100644 --- a/apps/web/src/index.html +++ b/apps/web/src/index.html @@ -20,6 +20,7 @@ +
-