Skip to content

Multi-Session

Craft Easy Admin supports connecting to multiple Craft Easy API instances simultaneously. Sessions persist across app restarts using AsyncStorage.

Session Model

Each connection is represented as an ApiSession:

interface ApiSession {
  id: string;                // Unique session ID (UUID)
  url: string;               // API base URL, e.g. "https://api.example.com"
  name: string;              // Display name (from schema.title)
  token: string | null;      // Auth token (null = not logged in)
  tokenExpiry: number | null; // Token expiry timestamp
  schema: AdminSchema | null; // Cached schema
  schemaUpdatedAt: number | null; // Last schema fetch timestamp
  theme: {
    primaryColor: string;
    logoUrl?: string;
  } | null;
  lastUsed: number;          // Last interaction timestamp
}

useSessionStore

A Zustand store backed by AsyncStorage for persistent session management.

State

interface SessionState {
  sessions: ApiSession[];         // All configured sessions
  activeSessionId: string | null; // Currently active session
  hydrated: boolean;              // AsyncStorage load complete
}

Computed Getters

getActiveSession(): ApiSession | null;   // Current active session
getSession(id: string): ApiSession | null; // Session by ID

Actions

Action Description
load() Restores sessions from AsyncStorage on app startup
addSession(url) Fetches schema from {url}/admin/schema, creates a session, sets it as active, persists
removeSession(id) Removes session, updates active if necessary, persists
switchSession(id) Sets active session, updates lastUsed timestamp, persists
updateToken(id, token, expiry) Updates auth token and expiry for a session, persists
updateSchema(id, schema) Updates cached schema and schemaUpdatedAt, updates name from schema.title, persists
clearToken(id) Clears token and expiry (logout), persists

Usage

import { useSessionStore } from '@craft-easy/admin/stores';

function SessionManager() {
  const { sessions, activeSessionId, addSession, switchSession, removeSession } = useSessionStore();

  const handleConnect = async () => {
    const session = await addSession('https://api.example.com');
    // session.schema is now populated, session is active
  };

  return (
    <View>
      {sessions.map(s => (
        <Pressable key={s.id} onPress={() => switchSession(s.id)}>
          <Text>{s.name} {s.id === activeSessionId ? '(active)' : ''}</Text>
          <Text>{s.token ? 'Authenticated' : 'Not logged in'}</Text>
        </Pressable>
      ))}
      <Button title="Add API" onPress={handleConnect} />
    </View>
  );
}

Session Lifecycle

1. Connect
   └─ User enters API URL
   └─ addSession(url) fetches /admin/schema
   └─ Session created with schema, no token

2. Login
   └─ User enters credentials
   └─ API returns auth token
   └─ updateToken(id, token, expiry)

3. Use
   └─ All API calls use active session's URL + token
   └─ Schema refreshed every 5 minutes via useAdminSchema()

4. Switch
   └─ switchSession(id) changes active context
   └─ UI re-renders with new session's schema and data

5. Logout
   └─ clearToken(id) removes auth token
   └─ Session remains (can re-login without re-fetching schema)

Persistence

Sessions are stored in AsyncStorage under the key craft-easy-sessions. The store:

  • Serializes all sessions as JSON on every mutation
  • Restores sessions via load() on app startup
  • Sets hydrated: true once restoration is complete
  • The UI should wait for hydrated before rendering session-dependent views

Session Switcher Component

The sidebar includes a SessionSwitcher component:

  • Hidden when only one session exists
  • Shows all sessions with authentication status indicator (green dot = authenticated)
  • Active session highlighted in primary color
  • Tap to switch between sessions