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.
This commit is contained in:
Johannes Millan 2026-01-22 17:33:17 +01:00
parent a8da38d840
commit 70aae24449
3 changed files with 18 additions and 14 deletions

View file

@ -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

View file

@ -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<void>();
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.

View file

@ -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