diff --git a/apps/web/src/app/server-storage/server-sign-in.component.ts b/apps/web/src/app/server-storage/server-sign-in.component.ts index 4fbc38baf..4a7d02a87 100644 --- a/apps/web/src/app/server-storage/server-sign-in.component.ts +++ b/apps/web/src/app/server-storage/server-sign-in.component.ts @@ -20,6 +20,7 @@ import { ServerStorageSession, } from '@iptvnator/services/server-storage'; import { rememberServer } from './server-storage-start'; +import { provideFormFieldDefaults } from '@iptvnator/ui/components/form-field-defaults'; import { PasswordVisibilityToggleDirective } from '@iptvnator/ui/components/password-visibility-toggle'; export type ServerSignInFeedback = 'rejected' | 'unreachable' | 'storage'; @@ -34,6 +35,7 @@ export type ServerSignInFeedback = 'rejected' | 'unreachable' | 'storage'; PasswordVisibilityToggleDirective, TranslatePipe, ], + providers: [provideFormFieldDefaults()], templateUrl: './server-sign-in.component.html', styleUrl: './server-sign-in.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/apps/web/src/app/services/pwa.service.ts b/apps/web/src/app/services/pwa.service.ts index b18fa6b7f..53583bfc7 100644 --- a/apps/web/src/app/services/pwa.service.ts +++ b/apps/web/src/app/services/pwa.service.ts @@ -15,11 +15,11 @@ import { } from 'rxjs'; import { DataService, + isBackendUrl, isGatewayOutage, isServerAccessChallenge, ServerStorageStatusService, } from '@iptvnator/services'; -import { isBackendUrl } from '@iptvnator/services/server-storage'; import { CONNECTIVITY_GUARD_RESET, ERROR, diff --git a/libs/services/src/lib/server-access-token.ts b/libs/services/src/lib/server-access-token.ts index 95810ff3e..1613eca4c 100644 --- a/libs/services/src/lib/server-access-token.ts +++ b/libs/services/src/lib/server-access-token.ts @@ -75,3 +75,29 @@ export function clearServerAccessToken(): void { // Nothing stored. } } + +/** + * Whether `url` goes to the backend: the same origin as the backend URL and + * a path inside it. A plain prefix check fails for `BACKEND_URL=/`, where + * every URL, other origins included, would carry the token. + */ +export function isBackendUrl( + url: string, + baseUrl: string, + pageUrl = globalThis.location?.href ?? 'http://localhost/' +): boolean { + try { + const base = new URL(baseUrl, pageUrl); + const target = new URL(url, pageUrl); + const root = base.pathname.endsWith('/') + ? base.pathname + : `${base.pathname}/`; + return ( + target.origin === base.origin && + (target.pathname === base.pathname || + target.pathname.startsWith(root)) + ); + } catch { + return false; + } +} diff --git a/libs/services/src/lib/server-storage/server-access-token.interceptor.ts b/libs/services/src/lib/server-storage/server-access-token.interceptor.ts index 7e9446a2d..ddf6ca1e2 100644 --- a/libs/services/src/lib/server-storage/server-access-token.interceptor.ts +++ b/libs/services/src/lib/server-storage/server-access-token.interceptor.ts @@ -8,9 +8,14 @@ import { import { inject, Injectable } from '@angular/core'; import { Observable, tap } from 'rxjs'; import { + isBackendUrl, isGatewayOutage, isServerAccessChallenge, } from '../server-access-token'; + +// Lives with the token helpers so the startup bundle can check URLs +// without loading the server storage code. +export { isBackendUrl } from '../server-access-token'; import { ServerStorageStatusService } from '../server-storage-status.service'; import { SERVER_STORAGE_CONFIG } from './server-storage.config'; @@ -67,29 +72,3 @@ export class ServerAccessTokenInterceptor implements HttpInterceptor { ); } } - -/** - * Whether `url` goes to the backend: the same origin as the backend URL and - * a path inside it. A plain prefix check fails for `BACKEND_URL=/`, where - * every URL, other origins included, would carry the token. - */ -export function isBackendUrl( - url: string, - baseUrl: string, - pageUrl = globalThis.location?.href ?? 'http://localhost/' -): boolean { - try { - const base = new URL(baseUrl, pageUrl); - const target = new URL(url, pageUrl); - const root = base.pathname.endsWith('/') - ? base.pathname - : `${base.pathname}/`; - return ( - target.origin === base.origin && - (target.pathname === base.pathname || - target.pathname.startsWith(root)) - ); - } catch { - return false; - } -} diff --git a/tools/performance/journey-baselines.json b/tools/performance/journey-baselines.json index 88bb7069e..c792a9d9a 100644 --- a/tools/performance/journey-baselines.json +++ b/tools/performance/journey-baselines.json @@ -3,12 +3,13 @@ "journeys": { "launch": { "renderer.initialBytes": { - "value": 1345807, + "value": 1357795, "unit": "bytes", "slack": 4096, - "updatedAt": "2026-10-13", - "evidencePr": 1931, - "measuredWith": "pnpm nx build web && pnpm run perf:initial-bytes" + "updatedAt": "2026-10-14", + "evidencePr": 1903, + "measuredWith": "pnpm nx build web && pnpm run perf:initial-bytes", + "note": "Raised by 11,988 bytes (0.9%) over #1931's 1,345,807 for self-hosted server storage: what must run before the first route stays on the startup path (the /v1/instance probe and remembered server, the connection status, the access token bound to its instance, the playlist storage backend with its per-playlist write serialization). The storage code itself (user-data sync, migration, sign-in, Settings > Server) loads by dynamic import in server mode only." }, "renderer.ipcCallsToFirstCard": { "value": 15, @@ -26,7 +27,7 @@ "updatedAt": "2026-10-12", "evidencePr": 1903, "measuredWith": "J1 launch A/B on macOS (pnpm run perf:journeys builds, launch journey only): master 6fe684af5 555, PR 1903 556 in every iteration; CI measured 569 on master", - "note": "Raised by 1 for self-hosted server storage: its new lazy chunks (server sign-in, server-storage providers, Settings > Server) split one shared initial chunk in two, so index.html carries one more modulepreload link, a node the parser inserts. Initial bytes stay within their baseline." + "note": "Raised by 1 for self-hosted server storage: its new lazy chunks (server sign-in, server-storage providers, Settings > Server) split one shared initial chunk in two, so index.html carries one more modulepreload link, a node the parser inserts. Initial bytes are raised separately (see renderer.initialBytes)." }, "main.modulesRegisteredBeforeWindow": { "value": 2,