feat: Implement session management and routing for general creation
- Added session initialization in main.ts to manage user sessions. - Updated router to include a new route for joining or creating a general. - Enhanced session store with methods for managing session tokens and user profiles. - Introduced new components for displaying city, general, nation, and command information. - Implemented loading states and error handling in the main view. - Created a skeleton loader component for better user experience during data fetching. - Updated TypeScript definitions for route metadata to include new authentication requirements.
This commit is contained in:
@@ -1,23 +1,186 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { gatewayTrpc } from '../utils/gatewayTrpc';
|
||||
import { trpc as gameTrpc } from '../utils/trpc';
|
||||
|
||||
export type SessionStatus = 'unknown' | 'public' | 'authed' | 'general';
|
||||
|
||||
export interface SessionUser {
|
||||
id: string;
|
||||
username: string;
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
interface SessionState {
|
||||
status: SessionStatus;
|
||||
user: SessionUser | null;
|
||||
sessionToken: string | null;
|
||||
gameToken: string | null;
|
||||
profile: string | null;
|
||||
initializing: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const SESSION_TOKEN_KEY = 'sammo-session-token';
|
||||
const PROFILE_KEY = 'sammo-game-profile';
|
||||
const GAME_TOKEN_KEY = 'sammo-game-token';
|
||||
|
||||
const readStorage = (key: string): string | null => {
|
||||
if (typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
return window.localStorage.getItem(key);
|
||||
};
|
||||
|
||||
const writeStorage = (key: string, value: string | null): void => {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
if (value) {
|
||||
window.localStorage.setItem(key, value);
|
||||
} else {
|
||||
window.localStorage.removeItem(key);
|
||||
}
|
||||
};
|
||||
|
||||
const readQueryParam = (key: string): string | null => {
|
||||
if (typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
const url = new URL(window.location.href);
|
||||
const value = url.searchParams.get(key);
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
url.searchParams.delete(key);
|
||||
window.history.replaceState({}, '', url.toString());
|
||||
return value;
|
||||
};
|
||||
|
||||
export const useSessionStore = defineStore('session', {
|
||||
state: (): SessionState => ({
|
||||
status: 'unknown',
|
||||
user: null,
|
||||
sessionToken: null,
|
||||
gameToken: null,
|
||||
profile: null,
|
||||
initializing: false,
|
||||
error: null,
|
||||
}),
|
||||
getters: {
|
||||
isReady: (state) => state.status !== 'unknown',
|
||||
isAuthed: (state) => state.status === 'authed' || state.status === 'general',
|
||||
hasGeneral: (state) => state.status === 'general',
|
||||
needsGeneral: (state) => state.status === 'authed',
|
||||
},
|
||||
actions: {
|
||||
setStatus(status: SessionStatus) {
|
||||
this.status = status;
|
||||
setSessionToken(sessionToken: string | null) {
|
||||
this.sessionToken = sessionToken;
|
||||
writeStorage(SESSION_TOKEN_KEY, sessionToken);
|
||||
},
|
||||
setProfile(profile: string | null) {
|
||||
this.profile = profile;
|
||||
writeStorage(PROFILE_KEY, profile);
|
||||
},
|
||||
setGameToken(gameToken: string | null) {
|
||||
this.gameToken = gameToken;
|
||||
writeStorage(GAME_TOKEN_KEY, gameToken);
|
||||
},
|
||||
clearSession() {
|
||||
this.user = null;
|
||||
this.setSessionToken(null);
|
||||
this.setGameToken(null);
|
||||
this.status = 'public';
|
||||
},
|
||||
async refreshGeneralStatus() {
|
||||
if (!this.gameToken) {
|
||||
this.status = 'authed';
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const lobby = await gameTrpc.lobby.info.query();
|
||||
this.status = lobby.myGeneral ? 'general' : 'authed';
|
||||
} catch {
|
||||
this.error = 'game_status_unavailable';
|
||||
}
|
||||
},
|
||||
async initialize() {
|
||||
if (this.initializing || this.status !== 'unknown') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.initializing = true;
|
||||
this.error = null;
|
||||
|
||||
const tokenFromQuery = readQueryParam('sessionToken');
|
||||
if (tokenFromQuery) {
|
||||
this.setSessionToken(tokenFromQuery);
|
||||
}
|
||||
|
||||
const profileFromQuery = readQueryParam('profile');
|
||||
if (profileFromQuery) {
|
||||
this.setProfile(profileFromQuery);
|
||||
}
|
||||
|
||||
const storedToken = this.sessionToken ?? readStorage(SESSION_TOKEN_KEY);
|
||||
if (storedToken && storedToken !== this.sessionToken) {
|
||||
this.setSessionToken(storedToken);
|
||||
}
|
||||
|
||||
const storedProfile = this.profile ?? readStorage(PROFILE_KEY) ?? import.meta.env.VITE_GAME_PROFILE;
|
||||
if (storedProfile && storedProfile !== this.profile) {
|
||||
this.setProfile(storedProfile);
|
||||
}
|
||||
|
||||
if (!this.sessionToken) {
|
||||
this.status = 'public';
|
||||
this.initializing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const me = await gatewayTrpc.me.query();
|
||||
if (!me) {
|
||||
this.clearSession();
|
||||
this.initializing = false;
|
||||
return;
|
||||
}
|
||||
this.user = {
|
||||
id: me.id,
|
||||
username: me.username,
|
||||
displayName: me.displayName,
|
||||
};
|
||||
this.status = 'authed';
|
||||
} catch {
|
||||
this.error = 'gateway_unavailable';
|
||||
this.status = 'public';
|
||||
this.initializing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.profile) {
|
||||
this.initializing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const sessionToken = this.sessionToken;
|
||||
if (!sessionToken) {
|
||||
this.status = 'public';
|
||||
this.initializing = false;
|
||||
return;
|
||||
}
|
||||
const issued = await gatewayTrpc.auth.issueGameSession.mutate({
|
||||
sessionToken,
|
||||
profile: this.profile,
|
||||
});
|
||||
this.setGameToken(issued.gameToken);
|
||||
await this.refreshGeneralStatus();
|
||||
} catch {
|
||||
this.error = 'game_session_unavailable';
|
||||
this.status = 'authed';
|
||||
} finally {
|
||||
this.initializing = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user