From 70aae244493a5b22b36063560b26b394191afeb7 Mon Sep 17 00:00:00 2001 From: Johannes Millan Date: Thu, 22 Jan 2026 17:33:17 +0100 Subject: [PATCH] fix(sync): defer ImmediateUploadService initialization until data loads Prevents race condition where upload attempts happen before sync config is loaded from IndexedDB, eliminating 404 errors to default baseUrl during app startup and E2E tests. --- .github/workflows/pr-preview-deploy.yml | 2 +- src/app/op-log/sync/immediate-upload.service.ts | 15 ++++++++++++++- src/main.ts | 15 +++------------ 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/.github/workflows/pr-preview-deploy.yml b/.github/workflows/pr-preview-deploy.yml index 6858a7cb5..ba8a74b76 100644 --- a/.github/workflows/pr-preview-deploy.yml +++ b/.github/workflows/pr-preview-deploy.yml @@ -33,7 +33,7 @@ jobs: ssh://git@github.com/ - name: Install npm Packages - run: npm ci + run: npm i - name: Generate environment file run: npm run env diff --git a/src/app/op-log/sync/immediate-upload.service.ts b/src/app/op-log/sync/immediate-upload.service.ts index ee5309215..92ae4ba09 100644 --- a/src/app/op-log/sync/immediate-upload.service.ts +++ b/src/app/op-log/sync/immediate-upload.service.ts @@ -1,11 +1,12 @@ import { inject, Injectable, OnDestroy } from '@angular/core'; import { Subject, Subscription } from 'rxjs'; -import { debounceTime, exhaustMap, filter } from 'rxjs/operators'; +import { debounceTime, exhaustMap, filter, take } from 'rxjs/operators'; import { isOnline } from '../../util/is-online'; import { SyncProviderManager } from '../sync-providers/provider-manager.service'; import { OperationLogSyncService } from './operation-log-sync.service'; import { isFileBasedProvider, isOperationSyncCapable } from './operation-sync.util'; import { OpLog } from '../../core/log'; +import { DataInitStateService } from '../../core/data-init/data-init-state.service'; const IMMEDIATE_UPLOAD_DEBOUNCE_MS = 2000; @@ -46,11 +47,23 @@ const IMMEDIATE_UPLOAD_DEBOUNCE_MS = 2000; export class ImmediateUploadService implements OnDestroy { private _providerManager = inject(SyncProviderManager); private _syncService = inject(OperationLogSyncService); + private _dataInitStateService = inject(DataInitStateService); private _uploadTrigger$ = new Subject(); private _subscription: Subscription | null = null; private _isInitialized = false; + constructor() { + // Initialize only after data is loaded to avoid race condition where + // upload attempts happen before sync config is loaded from IndexedDB. + // This prevents 404 errors to default baseUrl during app startup. + this._dataInitStateService.isAllDataLoadedInitially$ + .pipe(filter(Boolean), take(1)) + .subscribe(() => { + this.initialize(); + }); + } + /** * Initializes the immediate upload pipeline. * Call once after app initialization. diff --git a/src/main.ts b/src/main.ts index b4b67fec0..2aa57addd 100644 --- a/src/main.ts +++ b/src/main.ts @@ -41,7 +41,6 @@ import { StoreModule } from '@ngrx/store'; import { META_REDUCERS } from './app/root-store/meta/meta-reducer-registry'; import { setOperationCaptureService } from './app/root-store/meta/task-shared-meta-reducers'; import { OperationCaptureService } from './app/op-log/capture/operation-capture.service'; -import { ImmediateUploadService } from './app/op-log/sync/immediate-upload.service'; import { EffectsModule } from '@ngrx/effects'; import { StoreDevtoolsModule } from '@ngrx/store-devtools'; import { ReactiveFormsModule } from '@angular/forms'; @@ -182,17 +181,9 @@ bootstrapApplication(AppComponent, { deps: [OperationCaptureService], multi: true, }, - // Initialize immediate upload service for real-time sync to SuperSync - { - provide: APP_INITIALIZER, - useFactory: (immediateUploadService: ImmediateUploadService) => { - return () => { - immediateUploadService.initialize(); - }; - }, - deps: [ImmediateUploadService], - multi: true, - }, + // Note: ImmediateUploadService now initializes itself in constructor + // after DataInitStateService.isAllDataLoadedInitially$ fires to avoid + // race condition where upload attempts happen before sync config is loaded ], }).then(() => { // Initialize touch fix for Material menus