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: trueonce restoration is complete - The UI should wait for
hydratedbefore 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