mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-11 02:46:16 -08:00
feat(web): enable runtime PWA configuration
This commit is contained in:
1 parent
f0af34edef
commit
eafc95bef0
9 files changed
+110
-37
No files matched your search
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
window.__IPTVNATOR_CONFIG__ = window.__IPTVNATOR_CONFIG__ || {};
|
||||
@@ -20,6 +20,7 @@
|
||||
<link rel="manifest" href="manifest.webmanifest" />
|
||||
<link rel="apple-touch-icon" href="assets/icons/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/x-icon" href="assets/icons/favicon.ico" />
|
||||
<script src="assets/app-config.js"></script>
|
||||
<style>
|
||||
/* Inline splash — paints immediately while the JS bundle and the
|
||||
styles.css (~300KB) are still downloading and Angular is
|
||||
@@ -68,11 +69,7 @@
|
||||
</style>
|
||||
</head>
|
||||
<body class="mat-app-background">
|
||||
<div
|
||||
id="initial-splash"
|
||||
role="status"
|
||||
aria-label="Loading IPTVnator"
|
||||
>
|
||||
<div id="initial-splash" role="status" aria-label="Loading IPTVnator">
|
||||
<span class="splash-mark">IPTVnator</span>
|
||||
<span class="splash-spinner" aria-hidden="true"></span>
|
||||
</div>
|
||||
|
||||
Vendored
+3
@@ -37,6 +37,9 @@ declare global {
|
||||
}
|
||||
|
||||
interface Window {
|
||||
__IPTVNATOR_CONFIG__?: {
|
||||
BACKEND_URL?: string;
|
||||
};
|
||||
electron: {
|
||||
onPlaylistRefreshEvent?: (
|
||||
callback: (data: PlaylistRefreshEvent) => void
|
||||
|
||||
+29
-28
@@ -1,30 +1,31 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
|
||||
"index": "/index.html",
|
||||
"assetGroups": [
|
||||
{
|
||||
"name": "app",
|
||||
"installMode": "prefetch",
|
||||
"resources": {
|
||||
"files": [
|
||||
"/favicon.ico",
|
||||
"/index.html",
|
||||
"/manifest.webmanifest",
|
||||
"/*.css",
|
||||
"/*.js"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "assets",
|
||||
"installMode": "lazy",
|
||||
"updateMode": "prefetch",
|
||||
"resources": {
|
||||
"files": [
|
||||
"/assets/**",
|
||||
"/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
|
||||
"index": "/index.html",
|
||||
"assetGroups": [
|
||||
{
|
||||
"name": "app",
|
||||
"installMode": "prefetch",
|
||||
"resources": {
|
||||
"files": [
|
||||
"/favicon.ico",
|
||||
"/index.html",
|
||||
"/manifest.webmanifest",
|
||||
"/*.css",
|
||||
"/*.js"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "assets",
|
||||
"installMode": "lazy",
|
||||
"updateMode": "prefetch",
|
||||
"resources": {
|
||||
"files": [
|
||||
"/assets/**",
|
||||
"!/assets/app-config.js",
|
||||
"/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user