mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-25 00:47:39 +00:00
10 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9910d30fca
|
feat(sync): add OneDrive sync provider with PKCE auth (#7523)
* 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> |
||
|
|
f8317929dd
|
chore(deps): bump vitest from 3.2.4 to 4.1.6 (#7687)
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 3.2.4 to 4.1.6. - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.6/packages/vitest) --- updated-dependencies: - dependency-name: vitest dependency-version: 4.1.6 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
9f27d04056
|
chore(deps): bump @xmldom/xmldom from 0.8.12 to 0.9.10 (#7691)
Bumps [@xmldom/xmldom](https://github.com/xmldom/xmldom) from 0.8.12 to 0.9.10. - [Release notes](https://github.com/xmldom/xmldom/releases) - [Changelog](https://github.com/xmldom/xmldom/blob/master/CHANGELOG.md) - [Commits](https://github.com/xmldom/xmldom/compare/0.8.12...0.9.10) --- updated-dependencies: - dependency-name: "@xmldom/xmldom" dependency-version: 0.9.10 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
087b9dd43f
|
refactor(sync): post-extraction review cleanup of @sp/sync-core and @sp/sync-providers (#7595)
* refactor(sync): tighten extracted package surfaces
Combined polish from the post-extraction review:
- sync-core: strip NgRx-shaped types from EntityConfig/EntityRegistry;
expose host extensions via generic param. Move StateSelector,
PropsStateSelector, SelectByIdFactory, SelectById, EntityUpdateLike,
EntityAdapterLike to a new app-side entity-registry-host.types.ts.
- sync-core: mark OpType.SyncImport/BackupImport/Repair as @deprecated;
hosts should use createFullStateOpTypeHelpers().
- sync-providers: resolve provider.types.ts vs provider-types.ts
duplication; inline implementation into the dashed canonical name.
- sync-providers: drop unused root barrel and "." export; consumers
already use focused subpath barrels (/dropbox, /webdav, etc.).
- sync-providers: replace wildcard "@sp/sync-providers/*" tsconfig path
alias with 11 explicit subpath entries matching package.json exports;
deep-internal imports now fail at typecheck.
- sync-providers: move @sp/sync-core from dependencies to
peerDependencies (kept in devDependencies for tests).
- both packages: add composite: true to enable project references;
introduce tsconfig.build.json overlay so tsup DTS bundler still works.
- gitignore: ignore **/*.tsbuildinfo composite outputs.
* refactor(sync-core): prune 47 unused barrel exports
Removes exports with zero consumers outside the package. Source files
are unchanged; only the public barrel is trimmed. Covers compression
helper classes, sync-file-prefix error/config types, replay coordinator
internals, remote-apply result types, upload/download planning option
and plan types, ports misc, conflict-resolution helper types, and
sync-import-filter decision types.
* refactor(sync-core): drop unused encryption migration path
decryptWithMigration and DecryptResult had no host consumer; they
exposed a structural-migration entry point ("here is your ciphertext
re-encrypted under Argon2id") that nothing in the codebase reads. The
side-channel setLegacyKdfWarningHandler — which IS used — stays.
encryptWithDerivedKey/decryptWithDerivedKey lose their export keyword
and remain as module-internal helpers; encrypt/decrypt/encryptBatch/
decryptBatch still call them. Wire format and legacy-fallback semantics
are unchanged, so existing ciphertext continues to decrypt.
Test imports for compression and sync-file-prefix specs now go via
their source files instead of the trimmed barrel.
* fix(sync-providers): bound dropbox token refresh to single retry; share md5 rev helper
The five hand-rolled token-refresh blocks in Dropbox.{getFileRev,
downloadFile, uploadFile, removeFile, listFiles} recursed on themselves
after refresh. If the post-refresh call still saw a token error (real
case: the refresh token itself was revoked), the recursion would not
terminate. Consolidated into a single _withTokenRefresh helper that
attempts the call, refreshes once on a token error, retries once, then
lets the outer 401 classifier surface AuthFailSPError.
Same log message, same _isTokenError discriminator, same refresh call.
Same five sites still apply their post-call non-token error mapping
(NoRev, InvalidData, RemoteFileNotFound, path-not-found swallow, etc.).
Also extracts md5 content-rev computation duplicated between
LocalFileSyncBase._getLocalRev and WebdavApi._computeContentHash into a
shared file-based/content-rev.ts; both call sites preserve their own
error wrapping at the boundary.
* refactor(sync): split oversized super-sync and conflict-resolution
sync-providers: extract request-ID hashing from super-sync.ts (1017 ->
918 lines) into a new request-id.ts. The helpers were free functions
already in disguise (none referenced this), so the move is mechanical.
HTTP plumbing (_doWebFetch/_doNativeFetch/_fetchApi*) stays as private
methods — it transitively touches 12 instance members and would need
either a wide context object or a separate http-client collaborator
class to extract cleanly. Left as a follow-up.
sync-core: split conflict-resolution.ts into three cohesive files:
- entity-frontier.ts now owns buildEntityFrontier and
adjustForClockCorruption (per-entity vector-clock domain).
- extractEntityFromPayload and extractUpdateChanges move to
operation.types.ts next to the existing extractActionPayload.
- conflict-resolution.ts keeps deep-equality, LWW planning,
partitioning, and identical-conflict detection.
Public barrel exports unchanged; tests now import the moved symbols
from their new homes.
* refactor(sync-core): drop redundant OperationStorePort
OperationStorePort overlapped with RemoteOperationApplyStorePort on the
two state-transition methods (markSynced/markApplied,
markRejected/markFailed) and had zero non-structural consumers — the
only implementer was OperationLogStoreService, which already exposes
the three methods as its own public surface. Removing the port leaves
the service contract intact and removes the verb-pair confusion noted
in the post-extraction review.
Spec contract test still drives the same state transitions; only the
local typing of the test fixture changes from the deleted interface to
Pick<OperationLogStoreService, ...>.
* refactor(sync-providers): decouple SuperSync provider from SP-specific host
Two coupling leaks the package shouldn't carry:
1. SUPER_SYNC_DEFAULT_BASE_URL was an implicit fallback inside
SuperSyncProvider — an SP-specific URL baked into a "framework-
agnostic" package. Make defaultBaseUrl a required SuperSyncDeps
field; the host factory supplies the SP default. The constant stays
exported as a suggested default for hosts targeting the SP-hosted
server.
2. Consumers that wanted the WebSocket path had to do
`provider as unknown as SuperSyncProvider` to call
getWebSocketParams. Introduce SuperSyncWebSocketAccess interface +
isSuperSyncWebSocketAccess structural guard; SuperSyncProvider
implements it. sync-wrapper.service drops its cast in favor of the
guard.
super-sync-restore.service still casts to SuperSyncProvider for the
restore path — same pattern would solve it, but out of scope here.
* test(sync-providers): extract shared test helpers and prefer barrels
Adds tests/helpers/sync-logger.ts and tests/helpers/credential-store.ts
to centralize the noopLogger and CredentialStore mocks that were copy-
pasted across 8 spec files. createStatefulCredentialStore covers the
"load/upsert/clear with state" cases; createMockCredentialStore covers
bare vi.fn() ports. Spec sites that needed a unique mockResolvedValue
chain it after the helper, preserving behavior 1:1.
Also migrates 5 spec files from deep ../src/<file> paths to the
matching sub-barrel (../src/webdav, /http, /super-sync, /platform) for
symbols already exported there. No new barrel exports added — internal
types (WebDavHttpAdapter, WebdavApi, DropboxApi, etc.) stay on deep
paths because they are intentionally not part of the public surface.
super-sync.spec.ts keeps its own credential/logger mocks (special
__asPort wrapper and vi.spyOn against the live NOOP_SYNC_LOGGER) that
the generic helpers cannot reproduce without bloat.
* test(sync): pin vector-clock pruning, error-meta privacy, and sync-import edges
Fills three test gaps surfaced by the post-extraction review:
- vector-clock pruning correctness across clocks: 4 cases pinning that
pruning legitimately flips GREATER_THAN to CONCURRENT/LESS_THAN when
the dropped keys are still present in the comparison clock. This is
the documented behavior (compareVectorClocks is intentionally not
pruning-aware); the protocol handles flips server-side via the
rejected-ops retry loop. preserveClientIds case also covered.
- error-meta privacy boundary: 22 new cases covering urlPathOnly (strip
query/fragment/userinfo, preserve host+path+port, leave non-URLs
intact) and errorMeta (no leakage of headers, response bodies, OAuth
tokens, signed-URL params, user emails, or attached error fields).
Real negative assertions (.not.toContain), not shape checks.
- sync-import-filter edge cases: 8 cases covering empty clocks on
either side, op clock listing the import client at 0, same-client
with equal counter (pinning the strict-greater-than boundary), and
different-client knowledge above the import counter.
sync-core 195 -> 207 tests, sync-providers 319 -> 341 tests; no
production code changed.
* style(sync-core): format sync-file-prefix.spec import line
* fix(sync): address package review feedback
|
||
|
|
00098f52fb | refactor(sync-providers): add tiered package exports | ||
|
|
a11f27cf43 |
refactor(sync-providers): @xmldom/xmldom to devDeps + global DOMParser
Multi-review (W3) flagged that `@xmldom/xmldom` was a runtime
dependency only because the package's vitest env (Node) lacks the
DOM `DOMParser`. Browsers, Electron, and Capacitor WebViews all
provide their own `DOMParser` — so the dep was shipping into the
host's transitive bundle just to support the package's unit tests.
Move it to `devDependencies` and:
- Use `globalThis.DOMParser` at runtime in `webdav-xml-parser.ts`.
Added a minimal `declare const DOMParser: { new(): {
parseFromString(text, mimeType): XmlNodeLike } }` so the package's
strict TS still type-checks without DOM lib pulling in via
xmldom's `/// <reference lib="dom" />`.
- Polyfill `globalThis.DOMParser` from `@xmldom/xmldom` in a new
`tests/setup-dom-parser.ts` vitest setup file. Vitest config adds
it to `setupFiles`.
- Drop the explicit `import { DOMParser } from '@xmldom/xmldom'` from
the parser source.
Side effect: the package's `tsconfig.json` now sets `lib: ["ES2022",
"DOM"]`. Previously the `/// <reference lib="dom" />` directive
inside `@xmldom/xmldom`'s shipped `index.d.ts` was transparently
pulling DOM lib in for our TS DTS compilation (covering `Response`,
`RequestInit`, `URL`, `URLSearchParams`, `btoa`, etc.). Removing the
xmldom import broke that chain — the package code uses those DOM
globals throughout, so adding `DOM` to `lib` is the explicit fix.
Bundle impact
- Package's own ESM bundle: 73.23 KB (was 70.93 KB; +2.3 KB from
PR 6b's test-connection helper, not xmldom — xmldom was already
marked external by tsup since it was a dep, so the package bundle
never carried it).
- Host bundle savings: `@xmldom/xmldom` no longer transitively
installed for consumers — roughly 50 KB pre-gzip / ~15 KB gzip
recovered from app bundles.
Verification
- npm run sync-providers:test: 164/164 (setup file polyfills
DOMParser correctly; vitest reports 274 ms setup time).
- npm run sync-providers:build: ESM 73.23 KB / CJS 75.77 KB / DTS
37.13 KB. Confirmed `grep -c xmldom dist/index.mjs == 0`.
- All modified files pass `npm run checkFile`.
|
||
|
|
914122f134 |
refactor(sync-providers): move WebDAV + Nextcloud providers into package
Lift WebdavBaseProvider, Webdav, NextcloudProvider, WebdavApi,
WebDavHttpAdapter, and WebdavXmlParser into @sp/sync-providers behind
the existing port surface — no new ports introduced this slice (per
multi-review consensus).
Architecture
- App side: thin createWebdavProvider(extraPath?: string) /
createNextcloudProvider(extraPath?: string) factories compose
NativeHttpExecutor / WebFetchFactory / ProviderPlatformInfo /
SyncCredentialStore deps internally, matching the Dropbox slice
precedent. sync-providers.factory.ts updated to call the factories.
- Package side: WebdavBaseProvider is generic on a WebdavProviderId
union (typeof PROVIDER_ID_WEBDAV | typeof PROVIDER_ID_NEXTCLOUD),
eliminating the four `as unknown as` casts the original code used
to share its base class across WebDAV and Nextcloud cfgs.
- Native HTTP path stays correct: the app injects an APP_WEBDAV_NATIVE_HTTP
adapter (in capacitor-webdav-http/app-webdav-native-http.ts) that
wires Capacitor's WebDavHttp plugin into the existing
NativeHttpExecutor port. The package's WebDavHttpAdapter selects the
native path via platformInfo.isNativePlatform; otherwise it goes
through WebFetchFactory. The inline registerPlugin('WebDavHttp')
call in the previous adapter is gone — the canonical registration
in capacitor-webdav-http/index.ts (with the web fallback) is the
only one now.
Hashing
- md5HashSync (spark-md5) replaced with hash-wasm's async md5 (already
a package dep used by PKCE). _computeContentHash on WebdavApi is
now async; ripples through download / upload / verify paths.
- Added @xmldom/xmldom as a package dep so the parser can run under
vitest's Node test env. The parser uses getElementsByTagNameNS('*',
name) so it works portably across browser DOMParser and xmldom
(xmldom does not implement querySelector).
Privacy sweep
- urlPathOnly applied at every URL-bearing error-construction and log
site in webdav-http-adapter (incl. PotentialCorsError, the new
HttpNotOkAPIError synthetic 500, RemoteFileNotFoundAPIError).
- errorMeta(e, extra) replaces every raw `SyncLog.error(..., e)` site
across webdav-api and webdav-http-adapter — ten+ sites converted to
structured `SyncLogMeta` (errorName / errorCode / safe primitives).
- _buildFullPath now throws InvalidDataSPError with a generic
"contains '..' or '//'" message instead of generic Error echoing
the user-supplied path.
- WebdavXmlParser.validateResponseContent's log no longer carries
`responseSnippet: content.substring(0, 200)` — only `contentLength`
and operation name go through the logger.
- testConnection retains its user-facing fullUrl + e.message (the
user is testing their own server config — this is intentional UX,
not a log), but routes the same error through `errorMeta` for the
separate structured log line.
- CORS heuristic at webdav-http-adapter.ts:180-219 (40 lines) collapsed
to a 3-line `TypeError && message.includes('cors')` check. Closes
the privacy leak (Firefox's NetworkError embeds the request URL)
and the prior false-positive where plain offline/DNS errors fired
PotentialCorsError.
Spec migration
- Jasmine specs converted to Vitest:
- webdav-xml-parser.spec.ts (15 tests, was 40)
- webdav-http-adapter.spec.ts (11 tests, was 18)
- webdav-api.spec.ts (18 tests, was 44)
- webdav-base-provider.spec.ts (9 tests, was 26)
- The TestableWebDavHttpAdapter subclass-override pattern is deleted;
tests inject the WebDavHttpAdapterDeps (platformInfo, webFetch,
nativeHttp, logger) directly. Native-routed tests un-skip cleanly.
- The package-level __mocks__/@capacitor/core.ts harness is deleted
(no longer needed — the package never imports @capacitor/core).
Dialog-sync-cfg
- src/app/imex/sync/dialog-sync-cfg now imports WebdavApi +
WebDavHttpAdapter from @sp/sync-providers and constructs them with
app-supplied deps for the "Test connection" UX. The user-facing
success/error snackbar uses result.fullUrl + result.error
unchanged.
Verification
- npm run sync-providers:test: 157/157 (was 103; +54 webdav specs).
- npm run sync-providers:build: ESM 70.93 KB / CJS 73.78 KB / DTS
34.40 KB (was 40/43/25, ~30 KB growth from WebDAV + Nextcloud +
@xmldom/xmldom).
- npm run lint: clean.
- npm run test:file file-based-sync-adapter.service.spec.ts: 58/58.
- npm run test:file sync-wrapper.service.spec.ts: 107/107.
Slice scope per multi-review consensus
- Open Q1: dropped the proposed WebDavNativeHttpExecutor port —
reused NativeHttpExecutor with options. Open Q2: hash-wasm async.
Open Q3: Nextcloud generic widening to union. Open Q4: inline
registerPlugin dropped. Open Q5: CORS heuristic tightened in-slice.
Open Q6: no-retry behavior preserved. Open Q7: TestableWebDavHttpAdapter
deleted. Open Q8: webdav-api.spec kept conceptually monolithic
(the package-side rewrite is smaller, but no second-file split).
- Documented gemini-dissent decisions in the slice design doc.
Defers (per consensus, not in this slice):
- local-file-sync-base.ts md5HashPromise migration — for the LocalFile
slice.
- _directoryCreationQueue refactor — works; not premature.
- webdav-api file split into smaller modules — follow-up.
Follow-up testing not yet run by Claude (handover gates):
- Full npm test suite (two timezone variants).
- Full E2E.
- Manual round-trip against a real WebDAV / Nextcloud server (PUT
with If-Match rev, 412 conflict path, 401 reauth path, 404 fresh-
client bootstrap).
|
||
|
|
91e53bb488 |
refactor(sync-providers): move provider error classes
Lift 12 provider-shared error classes (AuthFailSPError, InvalidDataSPError,
HttpNotOkAPIError, NoRevAPIError, RemoteFileNotFoundAPIError,
MissingCredentialsSPError, MissingRefreshTokenAPIError,
TooManyRequestsAPIError, UploadRevToMatchMismatchAPIError,
PotentialCorsError, RemoteFileChangedUnexpectedly, EmptyRemoteBodySPError)
plus AdditionalLogErrorBase and extractErrorMessage into
@sp/sync-providers. App-side sync-errors.ts becomes a re-export shim
so existing call sites and instanceof checks keep working.
The moved AdditionalLogErrorBase drops its constructor-time
OP_LOG_SYNC_LOGGER.log side effect (Option A from the slice design):
privacy responsibility shifts entirely to catch-site logging via the
injected SyncLogger port. A new app-side identity spec asserts the
constructor identity is preserved across import paths so future bundler
or tsconfig drift can't silently break instanceof catches.
HttpNotOkAPIError splits its parsed body excerpt off .message onto a
new opt-in .detail field; getErrorTxt forwards .detail to UI surfaces
so user-visible toasts remain unchanged while privacy-aware logger
paths see only "HTTP <status> <statusText>".
TooManyRequestsAPIError's constructor is narrowed to accept only
{ status, retryAfter?, path? } — closing a latent bearer-token leak
where Dropbox's _handleErrorResponse passed the raw Authorization
header through additionalLog. Callers in dropbox-api and
webdav-http-adapter updated accordingly.
Package gains "sideEffects": false to unlock tree-shaking through the
barrel for consumers that import only error classes.
Slice design and round-2 multi-review findings documented in
docs/plans/2026-05-12-pr5-dropbox-slice.md.
|
||
|
|
0a891d5c36 | refactor(sync-providers): move pkce helper | ||
|
|
a97a15457b | refactor(sync-providers): scaffold provider package |