mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
perf(sync): lazy-load sync providers and repair utilities to reduce bundle size
Convert sync providers from eagerly instantiated array to lazy-loaded factories using dynamic imports. Make dataRepair and migrateLegacyBackup dynamically imported at their call sites (error/migration paths only). - getProviderById is now async with promise-cached instantiation - _setActiveProvider guards against redundant re-activation - Staleness counter prevents race conditions on rapid provider switches
This commit is contained in:
parent
3a5093f270
commit
7d27400d03
8 changed files with 180 additions and 120 deletions
|
|
@ -122,8 +122,9 @@ describe('SyncConfigService', () => {
|
|||
),
|
||||
},
|
||||
};
|
||||
// getProviderById returns synchronously, not a Promise
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
|
||||
const settings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
|
|
@ -152,8 +153,10 @@ describe('SyncConfigService', () => {
|
|||
});
|
||||
|
||||
it('should apply default values for LocalFile provider fields when no existing config', async () => {
|
||||
// Mock no existing provider - getProviderById returns synchronously
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(null);
|
||||
// Mock no existing provider
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(null),
|
||||
);
|
||||
|
||||
const settings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
|
|
@ -191,8 +194,9 @@ describe('SyncConfigService', () => {
|
|||
),
|
||||
},
|
||||
};
|
||||
// getProviderById returns synchronously, not a Promise
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
|
||||
const settings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
|
|
@ -231,8 +235,9 @@ describe('SyncConfigService', () => {
|
|||
),
|
||||
},
|
||||
};
|
||||
// getProviderById returns synchronously, not a Promise
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
|
||||
// Update settings without changing the provider
|
||||
const settings: SyncConfig = {
|
||||
|
|
@ -276,7 +281,9 @@ describe('SyncConfigService', () => {
|
|||
),
|
||||
},
|
||||
};
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
|
||||
// Simulate form state after resetOnHide: true triggered
|
||||
// Form only provides baseUrl, accessToken is empty string (reset by Formly)
|
||||
|
|
@ -319,7 +326,9 @@ describe('SyncConfigService', () => {
|
|||
),
|
||||
},
|
||||
};
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
|
||||
const settings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
|
|
@ -360,7 +369,9 @@ describe('SyncConfigService', () => {
|
|||
),
|
||||
},
|
||||
};
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
|
||||
// Form provides empty password (e.g., from resetOnHide or form state issue)
|
||||
const settings: SyncConfig = {
|
||||
|
|
@ -402,7 +413,9 @@ describe('SyncConfigService', () => {
|
|||
),
|
||||
},
|
||||
};
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
|
||||
// Form may include isEncryptionEnabled: false (e.g., stale Formly model)
|
||||
// but for SuperSync it should NOT override the saved config
|
||||
|
|
@ -443,7 +456,9 @@ describe('SyncConfigService', () => {
|
|||
),
|
||||
},
|
||||
};
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
|
||||
// Form provides only baseUrl, all other fields empty
|
||||
const settings: SyncConfig = {
|
||||
|
|
@ -488,7 +503,9 @@ describe('SyncConfigService', () => {
|
|||
),
|
||||
},
|
||||
};
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
|
||||
const settings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
|
|
@ -530,7 +547,9 @@ describe('SyncConfigService', () => {
|
|||
),
|
||||
},
|
||||
};
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
|
||||
// Form provides empty path
|
||||
const settings: SyncConfig = {
|
||||
|
|
@ -554,13 +573,15 @@ describe('SyncConfigService', () => {
|
|||
});
|
||||
|
||||
it('should prevent duplicate saves when settings are unchanged', async () => {
|
||||
// Mock provider for the test - getProviderById returns synchronously
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue({
|
||||
id: SyncProviderId.WebDAV,
|
||||
privateCfg: {
|
||||
load: jasmine.createSpy('load').and.returnValue(Promise.resolve({})),
|
||||
},
|
||||
});
|
||||
// Mock provider for the test
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve({
|
||||
id: SyncProviderId.WebDAV,
|
||||
privateCfg: {
|
||||
load: jasmine.createSpy('load').and.returnValue(Promise.resolve({})),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const settings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
|
|
@ -590,12 +611,14 @@ describe('SyncConfigService', () => {
|
|||
|
||||
it('should deduplicate when syncSettingsForm$ emits before Formly modelChange', async () => {
|
||||
// Mock provider for the test
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue({
|
||||
id: SyncProviderId.WebDAV,
|
||||
privateCfg: {
|
||||
load: jasmine.createSpy('load').and.returnValue(Promise.resolve({})),
|
||||
},
|
||||
});
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve({
|
||||
id: SyncProviderId.WebDAV,
|
||||
privateCfg: {
|
||||
load: jasmine.createSpy('load').and.returnValue(Promise.resolve({})),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// Simulate syncSettingsForm$ emission by pushing provider config
|
||||
mockCurrentProviderPrivateCfg$.next({
|
||||
|
|
@ -633,8 +656,10 @@ describe('SyncConfigService', () => {
|
|||
});
|
||||
|
||||
it('should handle provider with no existing config', async () => {
|
||||
// Mock no existing provider (e.g., initial setup) - getProviderById returns synchronously
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(null);
|
||||
// Mock no existing provider (e.g., initial setup)
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(null),
|
||||
);
|
||||
|
||||
const settings: SyncConfig = {
|
||||
isEnabled: true,
|
||||
|
|
@ -682,9 +707,11 @@ describe('SyncConfigService', () => {
|
|||
},
|
||||
};
|
||||
|
||||
// Mock: No provider exists initially - getProviderById returns synchronously
|
||||
// Mock: No provider exists initially
|
||||
(providerManager.getActiveProvider as jasmine.Spy).and.returnValue(null);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(null);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(null),
|
||||
);
|
||||
|
||||
// User saves the form
|
||||
await service.updateSettingsFromForm(initialSettings);
|
||||
|
|
@ -736,9 +763,11 @@ describe('SyncConfigService', () => {
|
|||
},
|
||||
};
|
||||
|
||||
// No provider exists yet - getProviderById returns synchronously
|
||||
// No provider exists yet
|
||||
(providerManager.getActiveProvider as jasmine.Spy).and.returnValue(null);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(null);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(null),
|
||||
);
|
||||
|
||||
await service.updateSettingsFromForm(initialSettings);
|
||||
|
||||
|
|
@ -802,14 +831,16 @@ describe('SyncConfigService', () => {
|
|||
},
|
||||
};
|
||||
|
||||
// Mock existing WebDAV provider - getProviderById returns synchronously
|
||||
// Mock existing WebDAV provider
|
||||
const mockProvider = {
|
||||
id: SyncProviderId.WebDAV,
|
||||
privateCfg: {
|
||||
load: jasmine.createSpy('load').and.returnValue(Promise.resolve({})),
|
||||
},
|
||||
};
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
|
||||
await service.updateSettingsFromForm(webDavSettings);
|
||||
|
||||
|
|
@ -841,9 +872,11 @@ describe('SyncConfigService', () => {
|
|||
},
|
||||
};
|
||||
|
||||
// Mock that there's no active provider yet (initial setup) - getProviderById returns synchronously
|
||||
// Mock that there's no active provider yet (initial setup)
|
||||
(providerManager.getActiveProvider as jasmine.Spy).and.returnValue(null);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(null);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(null),
|
||||
);
|
||||
|
||||
// Act: User saves the form with encryption enabled
|
||||
await service.updateSettingsFromForm(newSettings);
|
||||
|
|
@ -870,8 +903,10 @@ describe('SyncConfigService', () => {
|
|||
},
|
||||
};
|
||||
|
||||
// Update mocks to simulate provider is now available - getProviderById returns synchronously
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
// Update mocks to simulate provider is now available
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
|
||||
// In a real scenario, after setPrivateCfgForSyncProvider is called,
|
||||
// the currentProviderPrivateCfg$ would be updated with the saved config
|
||||
|
|
@ -1032,7 +1067,9 @@ describe('SyncConfigService', () => {
|
|||
),
|
||||
},
|
||||
};
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
(providerManager.getActiveProvider as jasmine.Spy).and.returnValue(mockProvider);
|
||||
|
||||
// Update password
|
||||
|
|
@ -1064,7 +1101,9 @@ describe('SyncConfigService', () => {
|
|||
),
|
||||
},
|
||||
};
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
(providerManager.getActiveProvider as jasmine.Spy).and.returnValue(mockProvider);
|
||||
|
||||
// Update password
|
||||
|
|
@ -1100,7 +1139,9 @@ describe('SyncConfigService', () => {
|
|||
),
|
||||
},
|
||||
};
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
(providerManager.getActiveProvider as jasmine.Spy).and.returnValue(mockProvider);
|
||||
|
||||
await service.updateEncryptionPassword('newpass', SyncProviderId.SuperSync);
|
||||
|
|
@ -1131,7 +1172,9 @@ describe('SyncConfigService', () => {
|
|||
.and.returnValue(Promise.resolve(existingConfig)),
|
||||
},
|
||||
};
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
(providerManager.getActiveProvider as jasmine.Spy).and.returnValue(mockProvider);
|
||||
|
||||
// Update password
|
||||
|
|
@ -1192,7 +1235,9 @@ describe('SyncConfigService', () => {
|
|||
getConfig: jasmine.createSpy('getConfig'),
|
||||
};
|
||||
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
(providerManager.getActiveProvider as jasmine.Spy).and.returnValue(mockProvider);
|
||||
|
||||
// Simulate form update with NO encryptKey (stale form model)
|
||||
|
|
@ -1240,7 +1285,9 @@ describe('SyncConfigService', () => {
|
|||
getConfig: jasmine.createSpy('getConfig'),
|
||||
};
|
||||
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
(providerManager.getActiveProvider as jasmine.Spy).and.returnValue(mockProvider);
|
||||
|
||||
// Simulate form update with OLD password (stale form model from before dialog)
|
||||
|
|
@ -1286,7 +1333,9 @@ describe('SyncConfigService', () => {
|
|||
getConfig: jasmine.createSpy('getConfig'),
|
||||
};
|
||||
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
(providerManager.getActiveProvider as jasmine.Spy).and.returnValue(mockProvider);
|
||||
|
||||
// Simulate form update with NEW password from form
|
||||
|
|
@ -1327,7 +1376,9 @@ describe('SyncConfigService', () => {
|
|||
getConfig: jasmine.createSpy('getConfig'),
|
||||
};
|
||||
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
(providerManager.getActiveProvider as jasmine.Spy).and.returnValue(mockProvider);
|
||||
|
||||
// Simulate form update with password in settings.encryptKey (legacy path)
|
||||
|
|
@ -1375,7 +1426,9 @@ describe('SyncConfigService', () => {
|
|||
getConfig: jasmine.createSpy('getConfig'),
|
||||
};
|
||||
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
(providerManager.getActiveProvider as jasmine.Spy).and.returnValue(mockProvider);
|
||||
|
||||
const formSettings: SyncConfig = {
|
||||
|
|
@ -1426,7 +1479,9 @@ describe('SyncConfigService', () => {
|
|||
getConfig: jasmine.createSpy('getConfig'),
|
||||
};
|
||||
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(mockProvider);
|
||||
(providerManager.getProviderById as jasmine.Spy).and.returnValue(
|
||||
Promise.resolve(mockProvider),
|
||||
);
|
||||
(providerManager.getActiveProvider as jasmine.Spy).and.returnValue(mockProvider);
|
||||
|
||||
// Step 2: Simulate stale form model arriving AFTER dialog saved password
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ describe('SyncWrapperService', () => {
|
|||
},
|
||||
);
|
||||
mockProviderManager.clearAuthCredentials.and.returnValue(Promise.resolve());
|
||||
mockProviderManager.getProviderById.and.returnValue(Promise.resolve(undefined));
|
||||
mockProviderManager.getLastSyncedProviderId.and.returnValue(null);
|
||||
mockProviderManager.getActiveProvider.and.returnValue({
|
||||
id: SyncProviderId.SuperSync,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import { Operation, OpType, ActionType } from '../core/operation.types';
|
|||
import { CURRENT_SCHEMA_VERSION } from '../persistence/schema-migration.service';
|
||||
import { uuidv7 } from '../../util/uuid-v7';
|
||||
import { loadAllData } from '../../root-store/meta/load-all-data.action';
|
||||
import { dataRepair } from '../validation/data-repair';
|
||||
import { isDataRepairPossible } from '../validation/is-data-repair-possible.util';
|
||||
import { OpLog } from '../../core/log';
|
||||
import {
|
||||
|
|
@ -19,7 +18,6 @@ import {
|
|||
import { CompleteBackup } from '../core/types/sync.types';
|
||||
import { ArchiveDbAdapter } from '../../core/persistence/archive-db-adapter.service';
|
||||
import { ArchiveModel } from '../../features/archive/archive.model';
|
||||
import { isLegacyBackupData, migrateLegacyBackup } from './migrate-legacy-backup';
|
||||
|
||||
/**
|
||||
* Service for handling backup import and export operations.
|
||||
|
|
@ -91,6 +89,8 @@ export class BackupService {
|
|||
}
|
||||
|
||||
// 2. Migrate legacy backups (pre-v14) that have the old data shape
|
||||
const { isLegacyBackupData, migrateLegacyBackup } =
|
||||
await import('./migrate-legacy-backup');
|
||||
if (isLegacyBackupData(backupData as unknown as Record<string, unknown>)) {
|
||||
OpLog.normal(
|
||||
'BackupService: Detected legacy backup format, running migration...',
|
||||
|
|
@ -121,6 +121,7 @@ export class BackupService {
|
|||
'errors' in validationResult.typiaResult
|
||||
? validationResult.typiaResult.errors
|
||||
: [];
|
||||
const { dataRepair } = await import('../validation/data-repair');
|
||||
validatedData = dataRepair(backupData, errors).data;
|
||||
} else {
|
||||
throw new Error('Data validation failed and repair not possible');
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import {
|
|||
import { loadAllData } from '../../root-store/meta/load-all-data.action';
|
||||
import { download } from '../../util/download';
|
||||
import { isDataRepairPossible } from '../validation/is-data-repair-possible.util';
|
||||
import { dataRepair } from '../validation/data-repair';
|
||||
import { uuidv7 } from '../../util/uuid-v7';
|
||||
import { ActionType, Operation, OpType } from '../core/operation.types';
|
||||
import { CURRENT_SCHEMA_VERSION } from './schema-migration.service';
|
||||
|
|
@ -220,6 +219,7 @@ export class OperationLogMigrationService {
|
|||
'errors' in validationResult.typiaResult
|
||||
? validationResult.typiaResult.errors
|
||||
: [];
|
||||
const { dataRepair } = await import('../validation/data-repair');
|
||||
dataToMigrate = dataRepair(legacyData as unknown as AppDataComplete, errors).data;
|
||||
|
||||
// Re-validate after repair to ensure success
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@ export class SyncProviderManager {
|
|||
// Lazily loaded providers (cached after first load)
|
||||
private _providers: SyncProviderBase<SyncProviderId>[] | null = null;
|
||||
|
||||
/** Counter to detect stale provider activations */
|
||||
private _activeProviderSetupId = 0;
|
||||
|
||||
// Current active provider
|
||||
private _activeProvider: SyncProviderBase<SyncProviderId> | null = null;
|
||||
private _activeProviderId$ = new BehaviorSubject<SyncProviderId | null>(null);
|
||||
|
|
@ -283,6 +286,12 @@ export class SyncProviderManager {
|
|||
* Sets the active sync provider (loads providers lazily on first call)
|
||||
*/
|
||||
private _setActiveProvider(providerId: SyncProviderId | null): void {
|
||||
// Skip if provider hasn't changed to avoid resetting state on unrelated config changes
|
||||
if (providerId === this._activeProviderId$.getValue()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const setupId = ++this._activeProviderSetupId;
|
||||
this._activeProviderId$.next(providerId);
|
||||
|
||||
if (!providerId) {
|
||||
|
|
@ -292,57 +301,37 @@ export class SyncProviderManager {
|
|||
return;
|
||||
}
|
||||
|
||||
// Load providers lazily and set the active one
|
||||
this._ensureProviders()
|
||||
.then((providers) => {
|
||||
// Guard against stale resolution if provider changed while loading
|
||||
if (this._activeProviderId$.getValue() !== providerId) {
|
||||
// Clear stale config from previous provider during async load
|
||||
this._currentProviderPrivateCfg$.next(null);
|
||||
|
||||
this.getProviderById(providerId)
|
||||
.then(async (provider) => {
|
||||
if (this._activeProviderSetupId !== setupId) {
|
||||
return;
|
||||
}
|
||||
const provider = providers.find((p) => p.id === providerId);
|
||||
if (provider) {
|
||||
this._activeProvider = provider;
|
||||
provider
|
||||
.isReady()
|
||||
.then((ready) => {
|
||||
if (this._activeProviderId$.getValue() !== providerId) return;
|
||||
this._isProviderReady$.next(ready);
|
||||
})
|
||||
.catch((err) => {
|
||||
SyncLog.err(
|
||||
'SyncProviderManager: Failed to check provider readiness:',
|
||||
err,
|
||||
);
|
||||
if (this._activeProviderId$.getValue() === providerId) {
|
||||
this._isProviderReady$.next(false);
|
||||
}
|
||||
});
|
||||
|
||||
// Emit provider config to observable
|
||||
provider.privateCfg
|
||||
.load()
|
||||
.then((privateCfg) => {
|
||||
if (this._activeProviderId$.getValue() !== providerId) return;
|
||||
this._currentProviderPrivateCfg$.next({
|
||||
providerId,
|
||||
privateCfg,
|
||||
});
|
||||
})
|
||||
.catch((err) =>
|
||||
SyncLog.err('SyncProviderManager: Failed to load provider config:', err),
|
||||
);
|
||||
|
||||
SyncLog.normal(`SyncProviderManager: Active provider set to ${providerId}`);
|
||||
} else {
|
||||
if (!provider) {
|
||||
SyncLog.err(`SyncProviderManager: Provider not found: ${providerId}`);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
if (this._activeProviderId$.getValue() !== providerId) {
|
||||
return;
|
||||
}
|
||||
SyncLog.err('SyncProviderManager: Failed to load providers:', err);
|
||||
this._isProviderReady$.next(false);
|
||||
this._activeProvider = provider;
|
||||
|
||||
const [ready, privateCfg] = await Promise.all([
|
||||
provider.isReady(),
|
||||
provider.privateCfg.load(),
|
||||
]);
|
||||
|
||||
if (this._activeProviderSetupId !== setupId) {
|
||||
return;
|
||||
}
|
||||
this._isProviderReady$.next(ready);
|
||||
this._currentProviderPrivateCfg$.next({ providerId, privateCfg });
|
||||
SyncLog.normal(`SyncProviderManager: Active provider set to ${providerId}`);
|
||||
})
|
||||
.catch((e) => {
|
||||
SyncLog.err(`SyncProviderManager: Failed to load provider ${providerId}:`, e);
|
||||
if (this._activeProviderSetupId === setupId) {
|
||||
this._isProviderReady$.next(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { inject, Injectable } from '@angular/core';
|
|||
import { IValidation } from 'typia';
|
||||
import { Action, Store } from '@ngrx/store';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { dataRepair } from './data-repair';
|
||||
import { isDataRepairPossible } from './is-data-repair-possible.util';
|
||||
import { RepairSummary } from '../core/operation.types';
|
||||
import { OpLog } from '../../core/log';
|
||||
|
|
@ -295,6 +294,7 @@ export class ValidateStateService {
|
|||
// User confirmed - proceed with repair
|
||||
try {
|
||||
const typiaErrors = validationResult.typiaErrors as IValidation.IError[];
|
||||
const { dataRepair } = await import('./data-repair');
|
||||
const repairResult = dataRepair(state as AppDataComplete, typiaErrors);
|
||||
const repairedState = repairResult.data;
|
||||
const repairSummary = repairResult.repairSummary;
|
||||
|
|
|
|||
|
|
@ -37,9 +37,13 @@ describe('ConfigPageComponent', () => {
|
|||
},
|
||||
{
|
||||
provide: SyncProviderManager,
|
||||
useValue: jasmine.createSpyObj('SyncProviderManager', ['getProviderById'], {
|
||||
currentProviderPrivateCfg$: of(null),
|
||||
}),
|
||||
useValue: (() => {
|
||||
const spy = jasmine.createSpyObj('SyncProviderManager', ['getProviderById'], {
|
||||
currentProviderPrivateCfg$: of(null),
|
||||
});
|
||||
spy.getProviderById.and.returnValue(Promise.resolve(undefined));
|
||||
return spy;
|
||||
})(),
|
||||
},
|
||||
{
|
||||
provide: GlobalConfigService,
|
||||
|
|
|
|||
|
|
@ -397,22 +397,26 @@ export class ConfigPageComponent implements OnInit, OnDestroy {
|
|||
// We need to check auth status asynchronously, so we'll set initial state
|
||||
// and update it when the form is rendered
|
||||
let isAuthenticated = false;
|
||||
dropboxProvider?.isReady().then((ready) => {
|
||||
isAuthenticated = ready;
|
||||
// Update the status field text after checking
|
||||
const statusField = item.fieldGroup?.find(
|
||||
(f: any) => f.key === 'authStatus',
|
||||
) as any;
|
||||
if (statusField?.templateOptions) {
|
||||
statusField.templateOptions.text = isAuthenticated
|
||||
? '✓ ' +
|
||||
this._translateService.instant(T.F.SYNC.FORM.DROPBOX.STATUS_CONFIGURED)
|
||||
: '⚠ ' +
|
||||
this._translateService.instant(
|
||||
T.F.SYNC.FORM.DROPBOX.STATUS_NOT_CONFIGURED,
|
||||
);
|
||||
}
|
||||
});
|
||||
this._providerManager
|
||||
.getProviderById(SyncProviderId.Dropbox)
|
||||
.then(async (dropboxProvider) => {
|
||||
const ready = await dropboxProvider?.isReady();
|
||||
isAuthenticated = !!ready;
|
||||
// Update the status field text after checking
|
||||
const statusField = item.fieldGroup?.find(
|
||||
(f: any) => f.key === 'authStatus',
|
||||
) as any;
|
||||
if (statusField?.templateOptions) {
|
||||
statusField.templateOptions.text = isAuthenticated
|
||||
? '✓ ' +
|
||||
this._translateService.instant(T.F.SYNC.FORM.DROPBOX.STATUS_CONFIGURED)
|
||||
: '⚠ ' +
|
||||
this._translateService.instant(
|
||||
T.F.SYNC.FORM.DROPBOX.STATUS_NOT_CONFIGURED,
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((e) => console.error('Failed to check Dropbox auth status:', e));
|
||||
|
||||
return {
|
||||
...item,
|
||||
|
|
@ -428,6 +432,9 @@ export class ConfigPageComponent implements OnInit, OnDestroy {
|
|||
onClick: async (_field: unknown, _form: unknown, model: unknown) => {
|
||||
try {
|
||||
// Check current auth status before opening dialog
|
||||
const dropboxProvider = await this._providerManager.getProviderById(
|
||||
SyncProviderId.Dropbox,
|
||||
);
|
||||
const isCurrentlyAuth = await dropboxProvider?.isReady();
|
||||
|
||||
const result =
|
||||
|
|
@ -491,7 +498,10 @@ export class ConfigPageComponent implements OnInit, OnDestroy {
|
|||
hooks: {
|
||||
// Update button text based on auth status when form initializes
|
||||
onInit: async (field: any) => {
|
||||
const isAuth = await dropboxProvider?.isReady();
|
||||
const dropboxProv = await this._providerManager.getProviderById(
|
||||
SyncProviderId.Dropbox,
|
||||
);
|
||||
const isAuth = await dropboxProv?.isReady();
|
||||
if (field?.templateOptions && isAuth) {
|
||||
field.templateOptions.text = T.F.SYNC.FORM.DROPBOX.BTN_REAUTHENTICATE;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue