- Unify duplicated HTTP methods in SuperSyncProvider into shared helpers
- Remove 5 encryption DI injection tokens, use private properties for test spying
- Remove DerivedKeyCacheService wrapper, use direct imports
- Cache _getServerSeqKey() result in SuperSyncProvider
- Split SyncProviderServiceInterface into SyncProviderBase + FileSyncProvider
- Remove dead file-op stubs from SuperSync provider
- Extract shared delete-and-reupload pattern into SnapshotUploadService
- Extract helper methods from operation-log-sync.service.ts
- Auto-invalidate WrappedProviderService cache on config changes
- Fix: preserve auth credentials in encryption toggle revert paths
- Fix: add config revert on disableEncryption failure
~720 lines net reduction across 26 files.
Implement end-to-end encryption for SuperSync with the following:
- Make encryption mandatory for SuperSync provider (optional for others)
- Add encryption setup during initial SuperSync configuration
- Add password strength indicator and toggle visibility in dialogs
- Probe server for encrypted data before showing encryption dialog
- Handle encryption state preservation during file import
- Prevent duplicate password dialogs and cascade between clients
- Simplify encryption codebase by removing dead code and deduplicating
- Add password-strength UI component
- Default sync provider to SuperSync in initial setup dialog
- Remove legacy dialog components (incomplete-sync, incoherent-timestamps)
Use providerManager.setProviderConfig() instead of direct
setPrivateCfg() in ImportEncryptionHandlerService so the
currentProviderPrivateCfg$ observable is updated. Without this,
the settings form still showed stale encryption buttons after
importing a backup with different encryption settings.
WebCrypto (crypto.subtle) is unavailable in insecure contexts like
Android Capacitor (serves from http://localhost). Previously, encryption
operations would fail silently, making sync appear successful when data
wasn't actually syncing.
Changes:
- Add isCryptoSubtleAvailable() check function in encryption.ts
- Add assertCryptoSubtleAvailable() guard to encrypt/decrypt functions
- Add WebCryptoNotAvailableError handler in sync-wrapper.service.ts
- Check crypto availability BEFORE deleting server data in
encryption-enable.service.ts and import-encryption-handler.service.ts
- Add WEB_CRYPTO_NOT_AVAILABLE translation key with clear error message
This ensures users see a clear error message instead of silent failure
when trying to use encryption on unsupported platforms.
* refactor(sync): extract shared snapshot upload logic into SnapshotUploadService
Extract duplicated snapshot upload mechanics from three services into
a shared SnapshotUploadService:
- EncryptionDisableService (126 → 84 lines)
- EncryptionEnableService (140 → 98 lines)
- ImportEncryptionHandlerService (263 → 236 lines)
The new service provides:
- getValidatedSuperSyncProvider(): Validates SuperSync provider is active
- gatherSnapshotData(): Gathers state, vector clock, client ID
- uploadSnapshot(): Low-level upload mechanics
- updateLastServerSeq(): Updates server sequence after upload
Config orchestration (timing, error recovery) remains in domain services.
This follows the reviewer's recommendation of "upload mechanics only"
extraction rather than over-abstracting the different behaviors.
* refactor(sync): extract shared dialog SCSS into _dialog-encryption-common.scss
Extract ~300 lines of duplicated SCSS across 4 encryption dialog
components into a shared mixin file.
Changes:
- Create _dialog-encryption-common.scss with reusable mixins:
- dialog-warning-box: Orange warning alerts
- dialog-info-box: Blue informational alerts
- dialog-info-section: Section with heading and list
- dialog-note: Inline note with icon
- dialog-note-multiline: Multiline note with flex-start alignment
- dialog-note-warn: Warning-colored note background
- dialog-spinner-inline: Inline spinner display
- dialog-encryption-common-classes: Convenience mixin for all common classes
- Refactor dialog components to use shared styles:
- dialog-disable-encryption: 88 → 3 lines
- dialog-enable-encryption: 88 → 3 lines
- dialog-change-encryption-password: 92 → 48 lines (keeps unique styles)
- dialog-import-encryption-warning: 136 → 73 lines (keeps unique styles)
Uses existing CSS variables (--color-warning, --c-dark-60, etc.) and
the darkTheme mixin for consistent theming.
* fix(sync): encrypt payload before upload in EncryptionEnableService
Fix pre-existing bug where EncryptionEnableService passed unencrypted
state to uploadSnapshot() while setting isPayloadEncrypted=true.
The bug was present since the service was created (commit e2db30c).
Other services like ImportEncryptionHandlerService and
OperationLogUploadService correctly encrypt before upload.
Changes:
- Inject OperationEncryptionService
- Call encryptPayload() before uploadSnapshot()
- Pass encrypted payload instead of raw state
* style: fix prettier formatting issues
* style: fix remaining prettier formatting issues
* style: fix prettier line break in spec file
* style: fix prettier formatting in spec file
* fix(sync): correct import paths for OperationSyncCapable, VectorClock, AppStateSnapshot
* test(sync): fix SnapshotUploadService unit test mocks
- Mock isReady as spy returning Promise<boolean> (not boolean property)
- Use supportsOperationSync property for isOperationSyncCapable check
- Remove non-existent save method from privateCfg mock
- Use regex matcher for error message assertion
---------
Co-authored-by: Claude <noreply@anthropic.com>
Critical fix: Fixed 3 additional services that still used sync getStateSnapshot()
instead of async version, which would cause archive data loss during encryption
operations.
Files fixed:
1. encryption-disable.service.ts (line 63)
- Disabling encryption would delete all archived tasks
2. encryption-enable.service.ts (line 68)
- Enabling encryption would delete all archived tasks
3. import-encryption-handler.service.ts (line 159)
- Changing encryption during import would delete all archived tasks
All three now use getStateSnapshotAsync() to properly load archives from
IndexedDB before creating SYNC_IMPORT operations.
Also fixed:
- Remove encryption button now properly disables during both operations
(password change AND encryption removal) for consistent UX
* feat(sync): add confirmation dialog when disabling SuperSync encryption
When users toggle off encryption for SuperSync, show a confirmation dialog
that explains the consequences:
- All encrypted sync data on the server will be deleted
- Current data will be re-uploaded without encryption
- Other devices will sync the unencrypted data
This ensures users understand that turning off encryption requires a fresh
snapshot to prevent mixing encrypted and unencrypted operations.
Changes:
- Add EncryptionDisableService to handle the disable encryption flow
- Add DialogDisableEncryptionComponent for user confirmation
- Add hook to encryption toggle in sync form to intercept disable action
- Add openDisableEncryptionDialog to dialog opener service
- Add translation keys for the new dialog
* fix(sync): initialize dialog opener and fix memory leak in encryption toggle
- Add EncryptionPasswordDialogOpenerInitService to initialize the module-level
dialog opener instance during app bootstrap
- Register the init service in main.ts using APP_INITIALIZER
- Fix memory leak by properly cleaning up subscription in onDestroy hook
- Add guard to prevent multiple dialogs opening simultaneously
- Fix race condition with async dialog handling
* feat(sync): handle encryption state change during backup import
When importing a backup file that has different encryption settings than
the current SuperSync configuration, the system now properly handles the
transition:
1. Detects encryption state change (enabled -> disabled or vice versa)
2. Deletes all server data (encrypted ops can't mix with unencrypted)
3. Uploads a fresh snapshot with the correct encryption settings
4. Updates the sync provider config to match the imported settings
This ensures a "tabula rasa" for the sync state when encryption changes,
preventing corrupt state where encrypted and unencrypted operations coexist.
Changes:
- Add ImportEncryptionHandlerService to detect and handle encryption changes
- Integrate the handler into FileImexComponent import flow
- Add E2E test for import with encryption state change scenario
* feat(sync): add confirmation dialog for import with encryption state change
Show a warning dialog to users when importing a backup that has different
encryption settings than their current SuperSync configuration. The dialog
explains the consequences:
- All server data will be deleted
- Sync history will be lost
- All devices will need to resync
This ensures users are informed before any encryption state change action.
* refactor(sync): improve UX of encryption state change dialogs
- Add context-specific messaging (enabling vs disabling encryption)
- Use visual state comparison with arrow indicator for import dialog
- Improve button label: "Import and Change Encryption" (clearer action)
- Add disableClose: true to prevent accidental dialog dismissal
- Improve terminology: "Your current configuration" → "After import"
- Clarify "resync" instructions for other devices
- Add aria-labels for screen reader accessibility
- Remove redundant warning icon from dialog title
* fix(sync): correct translation key path in import encryption warning dialog
Fix path from T.F.SYNC.IMPORT_ENCRYPTION_WARNING to
T.F.SYNC.FORM.IMPORT_ENCRYPTION_WARNING to match t.const.ts structure.
* fix(sync): use DI for OperationEncryptionService and fix tests
- Inject OperationEncryptionService properly instead of using `new`
which fails because the service uses Angular's inject() internally
- Add mock for ImportEncryptionHandlerService in FileImexComponent tests
* fix(ci): handle existing swap file in Lighthouse job
The Lighthouse job was failing with "fallocate: Text file busy" because
GitHub Actions runners may already have /swapfile in use. This fix:
- Disables existing swap file if present (swapoff)
- Removes the file before creating a new one
- Gracefully handles errors using || true
This resolves workflow failures while ensuring Lighthouse/Chrome still
get the required memory allocation.
---------
Co-authored-by: Claude <noreply@anthropic.com>