iOS: paginate listVaults(), actionable error copy, unregister push on sign-out, real-time vault WebSocket - #154
Open
willi-d7 wants to merge 1 commit into
Open
iOS: paginate listVaults(), actionable error copy, unregister push on sign-out, real-time vault WebSocket#154willi-d7 wants to merge 1 commit into
willi-d7 wants to merge 1 commit into
Conversation
…h on sign-out, real-time vault WebSocket
- APIClient.listVaults() takes cursor/limit query params and returns a page
plus an opaque next-page cursor (X-Next-Cursor response header, documented
in shared/api-contract.md). VaultStore exposes loadMore()/hasMorePages, and
VaultListView gets a "Load More" row. listAllVaults() (paginating internally)
replaces the old unpaginated call for BackgroundRefreshService/TTLWidget,
which need the complete vault set to compute TTL warnings correctly.
- APIError gains recoverySuggestion/isRetryable/suggestsContactSupport, and
ErrorPresentation turns those into a "Try Again" / "Contact Support"
affordance (ErrorActionView), wired into the auth flow and vault list load.
Decode failures are logged (redacted of tokens/secrets/credentials) via
DecodingFailureLogger for triage.
- AuthStore.signOut() now unregisters the device's push token before dropping
the auth token (KeychainService persists the last-registered token so
sign-out has something to unregister).
- Add VaultEventSocket: a URLSessionWebSocketTask-based client for the
documented `wss://.../v1/ws?vault_id={id}` real-time event stream, with
exponential-backoff reconnect and a `.fallbackToPolling` state once
reconnects are exhausted (existing pull-to-refresh/BackgroundRefreshService
polling never depended on it). VaultDetailView subscribes for the vault
it's showing and applies incoming updates in place via VaultStore.applyUpdate.
Also fixes two pre-existing bad-merge regressions from PR ethos-protocol#137/ethos-protocol#138 that
independently broke compilation and were blocking verification of this work:
Stores.swift was missing refreshSingle/deposit/withdraw/updateBeneficiary/
applyUpdate (already called from Views.swift) and had a duplicate
scheduleReminders(); VaultDetailView was missing the ttlRemaining/showDeposit/
showWithdraw/showManageBeneficiary state, its custom init, and
refreshTTLPeriodically(), plus had a duplicate load2FAStatus().
Closes ethos-protocol#20, ethos-protocol#21, ethos-protocol#23, ethos-protocol#24
|
@willi-d7 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements four independent iOS issues from the same batch:
listVaults()#21 — PaginateAPIClient.listVaults()APIError.decodingFailedand GenericserverErrorActionable Copy #23 — Actionable copy + affordance forAPIError.decodingFailed/.serverErrorunregisterPushToken()on Sign-Out #24 — CallunregisterPushToken()on sign-outWhat changed
#21 — Pagination
APIClient.listVaults(cursor:limit:)now sendscursor/limitquery params and returns aVaultPage(vaults+nextCursor, parsed from a newX-Next-Cursorresponse header). The response body itself stays a bareVaultarray — backward compatible with any client that doesn't send pagination params.APIClient.listAllVaults()(paginates internally, concatenates every page) replaces the old unpaginated call forBackgroundRefreshServiceandTTLWidget, since both need the complete vault set to compute TTL warnings correctly — a single page isn't enough there.VaultStoregainsloadMore()/hasMorePages;VaultListViewgets a "Load More" row at the bottom of the list.shared/api-contract.mdunder "List Pagination" as the coordination point for the Android client (no Android changes in this PR — it currently calls the same bare-array/vaultsendpoint and is unaffected until it opts into the new params).#23 — Actionable error copy
APIErrorgainsrecoverySuggestion,isRetryable, andsuggestsContactSupport, replacing decode failures' generic "Invalid server response" with copy that tells the user what to actually do.ErrorPresentation(new) turns any error into{ message, recoverySuggestion, showsRetry, showsContactSupport };ErrorActionViewrenders it with "Try Again" / "Contact Support" (mailto) buttons. Wired into the auth flow (sign-in/register) andVaultListView(which previously didn't surfacevaultStore.errorto the user at all).DecodingFailureLoggerlogs decode failures (path, expected type, and the response body with known-sensitive fields liketoken/credential_id/signature/etc. redacted) for triage, mirroringDeepLinkLogger's existing pattern.#24 — Unregister push token on sign-out
NotificationService.handleDeviceToken(_:)now persists the token viaKeychainServiceonce registration succeeds.AuthStore.signOut()is nowasync: it unregisters the persisted push token (while the auth token is still valid, since the request needs it) before dropping the auth token locally. Best-effort — sign-out always completes locally even if the network call fails.#20 — Real-time vault events
VaultEventSocket(URLSessionWebSocketTask-based) connects towss://.../v1/ws?vault_id={id}, reconnects with exponential backoff (capped) on a drop, and reports.fallbackToPollingafter exhausting reconnect attempts — existing polling (pull-to-refresh,BackgroundRefreshService) never depended on the socket, so nothing else needs to change for the app to keep working when it's unavailable.VaultStore.subscribeToEvents(vaultID:)/unsubscribeFromEvents()wire incomingvault_updatedevents into the existingapplyUpdate(_:)in-place list update.VaultDetailViewsubscribes for the vault it's showing, torn down via the same.tasklifecycle as its TTL-refresh loop.{"type": "vault_updated", "vault": {...}}, unrecognizedtypes ignored) inshared/api-contract.md.Pre-existing bugs fixed (blocking, unrelated to the above)
Two bad merges from PR #137 and #138 had left
mainnot compiling, which I discovered while touching these same files and had to fix to verify anything in this PR:Stores.swift:VaultStorewas missingrefreshSingle/deposit/withdraw/updateBeneficiary/applyUpdate— all already called fromViews.swift— and had a duplicatescheduleReminders().VaultDetailView: missing thettlRemaining/showDeposit/showWithdraw/showManageBeneficiary@State, its custominit, andrefreshTTLPeriodically()— all referenced in its own body — plus a duplicateload2FAStatus().Both were restored to what the surrounding (already-merged) code clearly intended, using the pre-merge branch tips (
6a608cf,6a82bb0) as reference. No unrelated behavior changes.Known pre-existing issues (not fixed, out of scope for this batch)
Tests/EthosProtocolTests.swift'sTTLWidgetTestsconstructsVaultEntry(date:vaultName:ttlRemaining:isExpiringSoon:), missing the requiredvaultID:argument — a compile error predating this PR (confirmed onorigin/mainbefore any of my changes). This blocks the whole SPM test target from building until fixed; left untouched since it's unrelated to this batch.Tests/APIClientTests.swift'sAPIClient.makeTestInstanceusessetValue(forKey:)/responds(to:)onAPIClient, which doesn't inherit fromNSObject— those KVC calls don't exist on it, so this test file doesn't compile either, independent of the above. Also pre-existing; the one call site whose type changed underlistVaults()(Add Pagination Support tolistVaults()#21) was kept internally consistent, but the underlying KVC issue wasn't fixed (out of scope).Verification performed
No macOS/Xcode toolchain is available in this environment (
ios-ci.ymlrequiresmacos-latest; this sandbox is Linux with noswift/xcodebuild), so I could not runxcodebuild testorswift builddirectly. Verification was done via:VaultEventSocket/VaultStore, closure capture, Codable key mapping, etc.)listVaults(),execute(),decode(),VaultStore.error/AuthStore.errortype change,VaultDetailView.init) to confirm no stale usages remainproject.yml'sTTLWidgettarget's curated Services file list and addedDecodingFailureLogger.swift(needed transitively byAPIClient.decode())This PR's actual compilation and test results should be confirmed by CI on push.
Test plan
New/updated tests (all in the SPM
EthosProtocolTestsbundle unless noted):AuthErrorTests,ErrorPresentationTests,DecodingFailureLoggerTests— GiveAPIError.decodingFailedand GenericserverErrorActionable Copy #23 error copy, presentation, and redacted loggingVaultsPaginationTests,VaultStorePaginationTests— Add Pagination Support tolistVaults()#21 cursor/limit query building, next-cursor header parsing,loadMore()guard behaviorAuthStoreSignOutTests(+AuthStoreSignOutHostedTestsin the hosted target) — CallunregisterPushToken()on Sign-Out #24 sign-out unregisters the persisted push token, no-ops when there's none, still signs out locally on unregister failureReconnectBackoffTests,VaultEventSocketURLTests,VaultEventSocketTests,VaultStoreEventWiringTests— Implement the Documented WebSocket Real-Time Vault Event Stream #20 backoff delay growth/cap, wss:// URL construction, reconnect-until-fallback state machine (via an injected mockWebSocketTasking), and events applied in place throughVaultStoreSince CI requires macOS, please confirm the
iOS CIworkflow is green on this PR before merge.Closes #20
closes #21
closes #23
closes #24