diff --git a/src/app/imex/sync/sync-config.service.spec.ts b/src/app/imex/sync/sync-config.service.spec.ts index e35dca7384..de7d9d72d1 100644 --- a/src/app/imex/sync/sync-config.service.spec.ts +++ b/src/app/imex/sync/sync-config.service.spec.ts @@ -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 diff --git a/src/app/imex/sync/sync-wrapper.service.spec.ts b/src/app/imex/sync/sync-wrapper.service.spec.ts index 4b97a17e32..2bb5f3df5c 100644 --- a/src/app/imex/sync/sync-wrapper.service.spec.ts +++ b/src/app/imex/sync/sync-wrapper.service.spec.ts @@ -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, diff --git a/src/app/op-log/backup/backup.service.ts b/src/app/op-log/backup/backup.service.ts index 0696976792..6bb0c5f855 100644 --- a/src/app/op-log/backup/backup.service.ts +++ b/src/app/op-log/backup/backup.service.ts @@ -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)) { 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'); diff --git a/src/app/op-log/persistence/operation-log-migration.service.ts b/src/app/op-log/persistence/operation-log-migration.service.ts index 4eb952a7f7..5463fb71fc 100644 --- a/src/app/op-log/persistence/operation-log-migration.service.ts +++ b/src/app/op-log/persistence/operation-log-migration.service.ts @@ -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 diff --git a/src/app/op-log/sync-providers/provider-manager.service.ts b/src/app/op-log/sync-providers/provider-manager.service.ts index a82417a9e3..ef68b6b556 100644 --- a/src/app/op-log/sync-providers/provider-manager.service.ts +++ b/src/app/op-log/sync-providers/provider-manager.service.ts @@ -45,6 +45,9 @@ export class SyncProviderManager { // Lazily loaded providers (cached after first load) private _providers: SyncProviderBase[] | null = null; + /** Counter to detect stale provider activations */ + private _activeProviderSetupId = 0; + // Current active provider private _activeProvider: SyncProviderBase | null = null; private _activeProviderId$ = new BehaviorSubject(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); + } }); } } diff --git a/src/app/op-log/validation/validate-state.service.ts b/src/app/op-log/validation/validate-state.service.ts index 96aa74e4dd..ef56f9494f 100644 --- a/src/app/op-log/validation/validate-state.service.ts +++ b/src/app/op-log/validation/validate-state.service.ts @@ -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; diff --git a/src/app/pages/config-page/config-page.component.spec.ts b/src/app/pages/config-page/config-page.component.spec.ts index 76db3b3176..aee18d685c 100644 --- a/src/app/pages/config-page/config-page.component.spec.ts +++ b/src/app/pages/config-page/config-page.component.spec.ts @@ -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, diff --git a/src/app/pages/config-page/config-page.component.ts b/src/app/pages/config-page/config-page.component.ts index f9ee092c13..9a65de3dd5 100644 --- a/src/app/pages/config-page/config-page.component.ts +++ b/src/app/pages/config-page/config-page.component.ts @@ -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; }