diff --git a/src/app/imex/sync/dialog-get-and-enter-auth-code/dialog-get-and-enter-auth-code.component.ts b/src/app/imex/sync/dialog-get-and-enter-auth-code/dialog-get-and-enter-auth-code.component.ts index 5efa62102a..ca8278618c 100644 --- a/src/app/imex/sync/dialog-get-and-enter-auth-code/dialog-get-and-enter-auth-code.component.ts +++ b/src/app/imex/sync/dialog-get-and-enter-auth-code/dialog-get-and-enter-auth-code.component.ts @@ -53,7 +53,10 @@ export class DialogGetAndEnterAuthCodeComponent implements OnDestroy { T: typeof T = T; token?: string; - // Always use manual code entry flow (show input field) + // Always use manual code entry flow (show input field on all platforms). + // The automatic deep-link redirect flow was reverted: it requires the + // redirect URI to be registered in the Dropbox developer console, and + // Android may kill the app during auth, losing the in-memory code verifier. readonly isNativePlatform = false; private _authCodeSub?: Subscription; diff --git a/src/app/imex/sync/dialog-sync-initial-cfg/dialog-sync-initial-cfg.component.ts b/src/app/imex/sync/dialog-sync-initial-cfg/dialog-sync-initial-cfg.component.ts index aa9b8c271b..aebc2c01db 100644 --- a/src/app/imex/sync/dialog-sync-initial-cfg/dialog-sync-initial-cfg.component.ts +++ b/src/app/imex/sync/dialog-sync-initial-cfg/dialog-sync-initial-cfg.component.ts @@ -222,7 +222,16 @@ export class DialogSyncInitialCfgComponent implements AfterViewInit { await this.syncConfigService.updateSettingsFromForm(configToSave as SyncConfig, true); const providerId = toSyncProviderId(this._tmpUpdatedCfg.syncProvider); if (providerId && this._tmpUpdatedCfg.isEnabled) { - await this.syncWrapperService.configuredAuthForSyncProviderIfNecessary(providerId); + const { wasConfigured, authAttempted } = + await this.syncWrapperService.configuredAuthForSyncProviderIfNecessary( + providerId, + ); + + // If auth was attempted (dialog opened) but not completed (cancelled or failed), + // keep dialog open so the user can retry without losing their config. + if (authAttempted && !wasConfigured) { + return; + } } this._matDialogRef.close(); diff --git a/src/app/imex/sync/sync-wrapper.service.ts b/src/app/imex/sync/sync-wrapper.service.ts index a3b1980a5f..d7eff1f5c0 100644 --- a/src/app/imex/sync/sync-wrapper.service.ts +++ b/src/app/imex/sync/sync-wrapper.service.ts @@ -765,20 +765,20 @@ export class SyncWrapperService { async configuredAuthForSyncProviderIfNecessary( providerId: SyncProviderId, force = false, - ): Promise<{ wasConfigured: boolean }> { + ): Promise<{ wasConfigured: boolean; authAttempted: boolean }> { const provider = await this._providerManager.getProviderById(providerId); if (!provider) { - return { wasConfigured: false }; + return { wasConfigured: false, authAttempted: false }; } if (!provider.getAuthHelper) { - return { wasConfigured: false }; + return { wasConfigured: false, authAttempted: false }; } if (!force && (await provider.isReady())) { SyncLog.warn('Provider already configured'); - return { wasConfigured: false }; + return { wasConfigured: false, authAttempted: false }; } try { @@ -807,9 +807,9 @@ export class SyncWrapperService { setTimeout(() => { this.sync(); }, 1000); - return { wasConfigured: true }; + return { wasConfigured: true, authAttempted: true }; } else { - return { wasConfigured: false }; + return { wasConfigured: false, authAttempted: true }; } } } catch (error) { @@ -823,9 +823,9 @@ export class SyncWrapperService { type: 'ERROR', config: { duration: 0 }, }); - return { wasConfigured: false }; + return { wasConfigured: false, authAttempted: true }; } - return { wasConfigured: false }; + return { wasConfigured: false, authAttempted: false }; } /** diff --git a/src/app/op-log/sync-providers/file-based/dropbox/dropbox.ts b/src/app/op-log/sync-providers/file-based/dropbox/dropbox.ts index 81290f3d0a..1eb2518a19 100644 --- a/src/app/op-log/sync-providers/file-based/dropbox/dropbox.ts +++ b/src/app/op-log/sync-providers/file-based/dropbox/dropbox.ts @@ -314,11 +314,12 @@ export class Dropbox implements SyncProviderServiceInterface { * Uses crypto.subtle when available, otherwise falls back to hash-wasm. */ const sha256 = async (data: Uint8Array): Promise => { - if (typeof crypto !== 'undefined' && crypto.subtle !== undefined) { + if (typeof crypto !== 'undefined' && crypto.subtle != null) { return crypto.subtle.digest('SHA-256', data as BufferSource); }