From 176108b6820d83e5a796e71cf409307a082aee2d Mon Sep 17 00:00:00 2001 From: 4gray Date: Wed, 14 Oct 2026 14:58:25 +0200 Subject: [PATCH] fix(web): keep server storage off the startup bundle; form-field defaults on sign-in PwaService imported isBackendUrl from the @iptvnator/services/server-storage barrel, which pulled the whole storage subsystem (sync, migration, API client) into main, though server mode loads it by dynamic import. The helper now lives with the token helpers in @iptvnator/services (re-exported from the interceptor): renderer.initialBytes 1,392,902 -> 1,357,795 bytes locally. The remaining 11,988 bytes over #1931's baseline are what must run before the first route (instance probe, connection status, token bound to its instance, storage backend and write serialization); the baseline is raised with a note, under the PR's perf-baseline-increase label. The sign-in component provides the form-field defaults per component, as master's check (#1928) now requires. Co-Authored-By: Claude Opus 5.5 --- .../server-sign-in.component.ts | 2 ++ apps/web/src/app/services/pwa.service.ts | 2 +- libs/services/src/lib/server-access-token.ts | 26 ++++++++++++++++ .../server-access-token.interceptor.ts | 31 +++---------------- tools/performance/journey-baselines.json | 11 ++++--- 5 files changed, 40 insertions(+), 32 deletions(-) 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,