* feat(sync): integrate onedrive with pkce and operation log sync
* fix(sync): add oauth state validation and token refresh concurrency control
* fix(sync): refactor onedrive oauth cleanup and reduce download API calls
- Replace setInterval-based OAuth state pruning with on-demand
_pruneExpiredOAuthStates() to avoid background timer overhead
- Optimize downloadFile to read ETag from content response headers
first, falling back to a metadata request only when needed (reduces
API calls from 2 to 1 in the common case)
- Narrow OneDrive class interface from SyncProviderServiceInterface to
FileSyncProvider for stronger type guarantees
- Accept optional devPath constructor parameter for non-production
folder namespacing
- Extract isOneDriveClientIdRequired to a helper function in
sync-form.const.ts for readability
- Change default OneDrive sync folder name to 'Super Productivity'
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sync): address PR #7523 review feedback for OneDrive sync
Critical fixes:
- Detect invalid_grant in token refresh responses and clear credentials
- Reset _tokenRefreshInFlightPromise in clearAuthCredentials to prevent
stale refresh results from overwriting cleared state
- Re-read config before persisting refreshed tokens to avoid race with
concurrent clearAuthCredentials calls
Important fixes:
- Extract OAuth state management to shared oauth-state.util.ts to fix
circular dependency between OneDrive provider and callback handler
- Add 401 retry pattern (_is401Retry) matching Dropbox's approach:
on 401, proactively refresh token and retry, only clear credentials
if retry fails
- Remove unused auth status translation keys (AUTH_SUCCESS,
BTN_AUTHENTICATE, BTN_REAUTHENTICATE, REAUTH_SUCCESS,
STATUS_CONFIGURED, STATUS_NOT_CONFIGURED) and form props
- Remove unused requireAuth parameter from _cfgOrError
Minor fixes:
- Replace path-exposing log messages with structured logging
- Remove unused _formatHttpErrorDetails helper
- Return empty ETag fallback in getFileRev instead of throwing
- Add folder cache invalidation comment for path changes
- Add afterEach to restore global fetch in tests
- Add PKCE defence-in-depth comment in auth code dialog
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sync): use eslint-disable-next-line for @microsoft.graph.conflictBehavior
Replace spread+computed-property workaround with a plain property plus
an explicit eslint-disable-next-line comment. Clearer intent: the
naming violation is a Graph API requirement, not accidental.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sync): pre-save OneDrive form config in reauth() to prevent MissingCredentialsSPError
Re-auth was failing silently on Linux because getAuthHelper() reads
clientId from IndexedDB (privateCfg), but the form values were never
written before calling configuredAuthForSyncProviderIfNecessary().
- Extract shared BTN_REAUTHENTICATE and REAUTH_SUCCESS translation keys
- Add OneDrive to OAUTH_SYNC_PROVIDERS
- Pre-save form config in reauth() matching the existing save() flow
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sync): adapt TooManyRequestsAPIError to new constructor signature after rebase
* refactor(sync): migrate OneDrive provider to packages/sync-providers
Move OneDrive core logic into the shared sync-providers package,
matching the pattern used by Dropbox, WebDAV, and other providers.
The app-side code is now a thin adapter with a createOneDriveProvider()
factory function.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sync): re-throw MissingRefreshTokenAPIError from _requestOAuthToken catch block
The catch block in _requestOAuthToken was swallowing the
MissingRefreshTokenAPIError thrown after clearing credentials on
invalid_grant, causing it to fall through to a generic HttpNotOkAPIError
instead. Now re-throws MissingRefreshTokenAPIError explicitly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sync): address PR #7523 second review critical and important items
- Replace _is401Retry instance field with per-call parameter to prevent
concurrent 401 races (Critical #1)
- Fix _requestOAuthToken catch block to re-throw MissingRefreshTokenAPIError
instead of swallowing it (Critical #2)
- Fix _parseOAuthCallback defaulting unknown provider to 'unknown' instead
of 'dropbox' to prevent misrouting (Critical #3 + Important #6)
- Change conflictBehavior from 'replace' to 'fail' to prevent data loss
if a file exists with the same name as the folder (Critical #4)
- Await in-flight token refresh promise in clearAuthCredentials before
clearing, to close the refresh-during-clear race (Important #5)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sync): address PR #7523 review important and minor items
- Extract duplicate OneDrive pre-auth save block into
_persistOneDriveFormCfgBeforeAuth() method (Important #7)
- Add GET probe in _ensureSyncFolderExists to skip per-segment
creation when folder already exists (Important #9)
- Redact sensitive params (code, code_verifier, refresh_token,
access_token) from token endpoint and Graph error bodies
(Important #11)
- Remove targetPath from _mapAndThrow error messages to prevent
logging user content (Important #12)
- Add OAuth state validation in manual paste flow for OneDrive
(Important #13)
- Fix unknown provider routing in _parseOAuthCallback (Important #14)
- Guard Electron IPC listener against post-destroy emissions
(Important #15)
- Remove dead authStatus Formly field from sync-form.const.ts (Minor)
- Simplify Headers builder in _ensureSyncFolderExists (Minor)
- Fix spec afterEach to restore original fetch instead of undefined
(Minor)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sync): fix clearAuthCredentials race and add unit tests for OneDrive
Fix a race condition in clearAuthCredentials where _tokenRefreshInFlightPromise
was nulled AFTER awaiting, causing the invalid_grant handler's clearAuthCredentials
to wait on itself. Now captures the promise and nulls the field before awaiting.
Add 5 new unit tests covering critical code paths:
- 401 token refresh and retry
- 401 retry failure with credential clearing
- 400 invalid_grant credential clearing
- 412 precondition failed mapping
- Concurrent token refresh deduplication
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sync): address PR #7523 third review items
- Simplify clearAuthCredentials: null _tokenRefreshInFlightPromise without
awaiting to avoid deadlock when called from inside the refresh IIFE
- Add superproductivity:// scheme validation in Electron OAuth listener
- Fix invalid_grant test to assert MissingRefreshTokenAPIError
- Fix folder cache test to match GET-probe optimization
- Set setComplete default in beforeEach
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test(sync): fix OAuthCallbackHandler spec to expect 'unknown' provider
The _parseOAuthCallback now returns 'unknown' instead of 'dropbox' for
URLs without an explicit provider path segment.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sync): address PR #7523 fourth review items
- Handle generic 401 from token endpoint (clear creds + MissingRefreshTokenAPIError)
- Remove user-supplied paths from error constructors (rule 9 compliance)
- Soften clearAuthCredentials comment to reflect actual TOCTOU guarantee
- Tighten Electron scheme gate to match native path prefix
- Add scheme info to Electron rejection log
- Fix folder-cache test to assert GET probe count
- Remove redundant per-test setComplete stub
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sync): address PR #7523 fifth review items
- Scope invalid_grant/401 credential clearing to refresh_token grant only
(bad auth code exchange no longer wipes existing credentials)
- Redact OAuth code/state from Electron protocol handler logs
- Add @sp/sync-providers/onedrive TS path alias to tsconfig files
- Default undefined sync config fields instead of overwriting with undefined
- Handle OneDrive in provider-switch branch (preserve encryptKey)
- Update sync-config test expectations to match default values
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sync): address PR #7523 sixth review items
- Use If-None-Match: * for null-rev uploads to prevent concurrent overwrite
- Guard in-flight refresh against stale credentials (match refreshToken/clientId/tenantId)
- Gate OneDrive provider option behind Electron/Native (web CORS unsupported)
- Don't clear credentials on transient token endpoint errors (429/5xx)
- Preserve null syncProvider instead of defaulting to WebDAV
- Hydrate full OneDrive privateCfg on provider switch
- Remove duplicate syncInterval/isManualSyncOnly Formly controls
- Revert non-English locale changes (zh.json, zh-tw.json)
- Redact URL fragments (#) in Electron protocol handler logs
- listFiles rethrows non-404 errors instead of swallowing all
- Update 401 test expectations for new rethrow behavior
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sync): guard credential clearing against stale concurrent refresh
Prevent a stale in-flight refresh from wiping newer credentials after
a concurrent re-auth. The refresh IIFE now throws a plain Error (not
MissingRefreshTokenAPIError) when it detects config drift, and all
clearAuthCredentials() call sites now verify the stored config still
matches before clearing. Also fix listFiles() to handle 404 from
_requestJson (which throws HttpNotOkAPIError, not RemoteFileNotFoundAPIError).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sync): address PR #7523 eighth review items
- Replace If-None-Match:* with @microsoft.graph.conflictBehavior=fail query param for upload create-only semantics
- Add identity change detection in _persistOneDriveFormCfgBeforeAuth to clear tokens when useCustomApp/clientId/tenantId change
- Extract _clearIfConfigMatches helper to guard credential clearing against stale concurrent refresh
- Restrict folder probe fallthrough to 404 only, propagate other errors
- Restore locale files from upstream master
- Centralize IS_ONEDRIVE_SUPPORTED in onedrive-auth-mode.const.ts, use in factory and form config
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sync): address PR #7523 ninth review items
- Paginate listFiles via @odata.nextLink to avoid silent data loss
- Fix logger misuse (log -> normal with proper string message)
- Replace as any with OneDrivePrivateCfg in dialog-sync-cfg
- Throw NoRevAPIError when uploadFile response lacks eTag
- Omit undefined optional booleans from global config to prevent overwrite
- Move OneDrive dynamic import inside IS_ONEDRIVE_SUPPORTED gate
- Require state param for OneDrive full-URL paste (CSRF)
- Add id_token to sensitive keys for body redaction
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sync): add OneDrive type alias to electron tsconfig paths
The @sp/sync-providers/onedrive path alias was missing from
electron/tsconfig.electron.json, causing the Electron build to fail
with "Cannot find module '@sp/sync-providers/onedrive'". The alias was
already present in tsconfig.base.json but electron uses its own paths.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Fixes#6042 where app wouldn't launch from taskbar when minimize to tray
was enabled. The second-instance handler now uses showOrFocus() which
properly handles both hidden and minimized windows, matching the behavior
of the tray icon click handler.