The frontend component of PuffPastry, meant to interface with the backend, which in turn interfaces with the smart contract.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

13 KiB

sessionId
session-261005-211542-1soe

Requirements

Overview & Goals

To maximize application utility and deliver an optimal user experience, users should not have to manually reconnect their Freighter wallet or re-authenticate after every browser refresh. The objective is to enhance src/composables/useSession.ts to manage and persist wallet connection data and authentication tokens across page reloads, making session state globally available and reactive across all UI components.

Scope

  • In Scope:
    • Expanding src/composables/useSession.ts into a reactive session composable managing wallet address, user ID, auth token, and connection lifecycle.
    • Hydrating wallet and authentication state from localStorage on initial application load and browser refresh.
    • Providing centralized methods in useSession.ts for connecting, disconnecting, checking Freighter status, and formatting wallet displays.
    • Refactoring src/components/BrandBar.vue and src/App.vue to leverage useSession() for unified session state management.
    • Ensuring outgoing API requests via OpenAPI client continue to resolve persisted tokens seamlessly.
  • Out of Scope:
    • Modifying backend authentication endpoints or token validation mechanisms.
    • Modifying Freighter browser extension APIs or Stellar SDK internal logic.

User Stories

  • As a platform user, I want my wallet connection and authentication status to persist when I refresh the page so that I do not need to repeatedly click “Connect Wallet” and approve popups.
  • As a user, I want the header brand bar to immediately reflect my connected wallet address on page load so that I know my active session is valid.
  • As a developer, I want a single source of truth for wallet session state in useSession.ts so that components across the application can reliably access authentication details without duplicate storage or Freighter queries.

Functional Requirements

  • Automatic State Hydration: On application mount or composable initialization, useSession checks localStorage for existing authToken and cached wallet address, verifies Freighter extension availability, and synchronizes reactive state.
  • Unified Connect Flow: connectWallet() requests Freighter permission (setAllowed()), retrieves the public key (getAddress()), logs in via AuthService.loginFreighter, stores the token in localStorage, and updates reactive state.
  • Clean Disconnect Flow: disconnectWallet() removes authentication and wallet data from localStorage and resets reactive variables to unauthenticated state.
  • Reactive UI Synchronization: BrandBar.vue dynamically displays either “Connect Wallet” or the truncated address based on useSession reactive state.

Non-Functional Requirements

  • Resilience & Fault Tolerance: If storage is corrupted, inaccessible, or the JWT is invalid/expired, useSession safely resets state without throwing unhandled exceptions.
  • Performance: Hydration must occur synchronously/microtask-level during initial render so UI does not flicker between unauthenticated and authenticated states.

Technical Design

Current Implementation

  • src/composables/useSession.ts is currently a minimal stub containing only hasFreighter = async () => freighter.isConnected().
  • src/components/BrandBar.vue independently executes Freighter login, sets localStorage.setItem("authToken", ...), and updates a local component ref userText. When the user refreshes, this component state is lost and resets to 'Connect Wallet'.
  • src/App.vue calls freighter.getAddress() in onBeforeMount and stores it in an isolated local ref address.
  • src/utils/auth.ts provides getAuthToken(), parseJwt(), and getUserIdFromJwt(), retrieving tokens from localStorage.

Key Decisions

  • Shared Module-Level Reactive State in useSession.ts:
    • Decision: Maintain walletAddress, authToken, userId, isConnecting, and isInitialized as module-level refs within useSession.ts (consistent with patterns used in useModal.ts and useLoading.ts).
    • Rationale: Guarantees all consumer components share the exact same reactive session state without requiring redundant Pinia store overhead or desynchronized state copies.
  • Storage Strategy:
    • Decision: Persist authToken under the standard 'authToken' key in localStorage and derive/cache stellarAddress and userId directly from JWT payload parsing (parseJwt) and Freighter sync.
    • Rationale: Keeps localStorage minimal, avoids stale divergent data, and maintains full compatibility with src/main.ts (OpenAPI.TOKEN) and src/utils/auth.ts.
  • Eager Hydration & Verification:
    • Decision: Restore state immediately from localStorage on initial access, followed by asynchronous verification with Freighter (freighter.isAllowed()).
    • Rationale: Eliminates UI layout shifts or “flash of unauthenticated content” while still ensuring the browser extension remains connected.

Proposed Changes

  1. src/composables/useSession.ts:
    • Add reactive state: walletAddress: ref<string | null>, authToken: ref<string | null>, userId: ref<number | undefined>, isConnecting: ref<boolean>.
    • Add computed helpers: isAuthenticated: computed(() => !!authToken.value && !!walletAddress.value), truncatedAddress: computed(() => walletAddress.value ? truncateWallet(walletAddress.value) : '').
    • Implement initSession() to restore token from localStorage, decode JWT for stellarAddress / userId, and verify with Freighter if available.
    • Implement connectWallet() and disconnectWallet().
    • Implement hasFreighter() to check extension availability.
  2. src/components/BrandBar.vue:
    • Replace local userText logic with const { isAuthenticated, truncatedAddress, isConnecting, connectWallet, disconnectWallet } = useSession().
    • Compute button text: show truncatedAddress if authenticated, "Connecting..." if connecting, or "Connect Wallet".
  3. src/App.vue:
    • Initialize session via initSession() in onBeforeMount / onMounted.
    • Remove isolated, unused address ref.

Data Models / Contracts

export interface SessionState {
  walletAddress: string | null;
  authToken: string | null;
  userId: number | undefined;
  isAuthenticated: boolean;
  isConnecting: boolean;
  hasFreighter: () => Promise<boolean>;
  connectWallet: () => Promise<boolean>;
  disconnectWallet: () => void;
  initSession: () => Promise<void>;
  truncatedAddress: string;
}

Components

  • src/composables/useSession.ts: Core session state, persistence, and wallet connection management.
  • src/components/BrandBar.vue: Primary consumer for wallet connect button and truncated address display.
  • src/App.vue: Top-level application shell invoking session initialization during application bootstrap.
  • src/utils/auth.ts: Helper utilities utilized by useSession.ts for token resolution and JWT decoding.

File Structure

  • src/composables/useSession.ts (Modified: add full reactive session management and local persistence)
  • src/components/BrandBar.vue (Modified: wire to useSession)
  • src/App.vue (Modified: initialize session on mount and clean up duplicate state)

Architecture Diagram

graph LR
    subgraph UI Layer
        BB[BrandBar.vue]
        APP[App.vue]
        MOD[Proposal/Amendment Modals]
    end

    subgraph State & Session
        US[useSession.ts Composable]
        AUTH[src/utils/auth.ts]
    end

    subgraph External & Storage
        LS[(localStorage)]
        FR[Freighter Wallet Extension]
        API[AuthService / Backend]
    end

    APP -->|initSession| US
    BB -->|connect / display| US
    MOD -->|read wallet / auth| US
    US -->|read / write token| LS
    US -->|parse JWT| AUTH
    US -->|getAddress / setAllowed| FR
    US -->|loginFreighter| API

Risks

  • Freighter Extension Locked / Permission Denied: If a user locks their Freighter wallet or declines permission, useSession will catch the error, log a warning, and keep or revert the state cleanly without crashing the UI.
  • Corrupted Token in Storage: If localStorage contains an invalid JWT string, parseJwt() returns null, prompting useSession to clear the bad token and reset state.

Testing

Validation Approach

Verify that the user’s wallet connection and JWT authentication remain intact and reactive across page refreshes, and that connection/disconnection lifecycles function properly without regressions in API request authorization.

Key Scenarios

  1. Wallet Connection & Persistence Across Page Reload:
    • Click “Connect Wallet” in BrandBar.vue and approve connection.
    • Verify that the button updates to the truncated wallet address and authToken is saved in localStorage.
    • Refresh the browser (F5 / full reload).
    • Verify that on page load, BrandBar.vue immediately displays the truncated wallet address without requiring the user to click “Connect Wallet” again.
  2. Authenticated API Calls After Reload:
    • With an existing persisted session, refresh the browser and navigate to proposal details or submit a comment/amendment.
    • Verify that outgoing requests include the Authorization: Bearer <token> header without authentication failure.
  3. Session Disconnection / Logout:
    • Trigger disconnect (or simulate logout).
    • Verify that localStorage items are cleared, reactive state resets, and BrandBar.vue updates to “Connect Wallet”.
    • Refresh the page and confirm the unauthenticated state remains.

Edge Cases

  • Page Load with No Token: Initial load with empty localStorage renders “Connect Wallet” cleanly with no errors.
  • Freighter Extension Not Installed: hasFreighter() returns false and clicking connect displays a descriptive message rather than throwing unhandled rejections.
  • Expired / Malformed Token: Corrupted token in localStorage is safely purged during hydration.

Delivery Steps

✓ Step 1: Implement reactive wallet session state and persistence in useSession.ts

useSession.ts provides a centralized reactive store for wallet address, authentication token, user ID, and connection status, with automatic local persistence and hydration.

  • Define reactive module-level state variables (walletAddress, authToken, userId, isConnecting, isInitialized) to maintain shared session state across all consumer components.
  • Implement initSession() to restore saved credentials (authToken and walletAddress) from localStorage, validate with parseJwt(), and verify Freighter extension availability via freighter.isConnected() and freighter.isAllowed().
  • Implement connectWallet() to orchestrate permission request via freighter.setAllowed(), fetch public address via freighter.getAddress(), execute AuthService.loginFreighter, store authToken and walletAddress in localStorage, and update reactive session state.
  • Implement disconnectWallet() to clear stored tokens and wallet data from localStorage and reset reactive state.
  • Expose helper computed properties and functions including isAuthenticated, truncatedAddress, hasFreighter, and connection status flags.

✓ Step 2: Integrate useSession into BrandBar.vue and App.vue

BrandBar.vue and App.vue consume useSession.ts, automatically restoring and displaying the active wallet address upon reload without user intervention.

  • Update App.vue on-mount lifecycle to invoke initSession() from useSession, ensuring wallet and auth state are restored immediately during startup.
  • Refactor BrandBar.vue to bind wallet button text and connection trigger directly to useSession's isAuthenticated, truncatedAddress, isConnecting, and connectWallet / disconnectWallet.
  • Remove redundant direct Freighter API invocations in BrandBar.vue and App.vue in favor of centralized composable methods.
  • Ensure smooth fallback UI handling when Freighter extension is not installed or permissions are revoked.

✓ Step 3: Validate wallet persistence across page refreshes and edge cases

The complete wallet persistence flow is verified across reloads, active voting/proposal actions, and logout scenarios.

  • Test connecting a Freighter wallet and refreshing the browser: confirm that wallet address, truncated display text, and JWT bearer token are immediately retained.
  • Verify that authenticated API requests dispatched through generated service clients (CommentService, ProposalService, AmendmentService) continue to attach the bearer token after page reload.
  • Test edge cases such as corrupted localStorage tokens, expired JWTs, Freighter wallet disconnection, and missing browser extensions to ensure graceful fallback without runtime errors.