Authentication & Token Management: - Add deviceId to token refresh flow (backend requires both refreshToken and deviceId) - Fix React Strict Mode token clearing race condition with retry logic - Improve AuthContext to handle all token state combinations properly - Store deviceId in localStorage alongside tokens UI/UX Improvements: - Remove deprecated legacyBehavior from Next.js Link components - Update primary theme color to WCAG AA compliant #7c3aed - Fix nested button error in TabBar voice navigation - Fix invalid Tabs value error in DynamicChildDashboard Multi-Child Dashboard: - Load all children into Redux store properly - Fetch metrics for all children, not just selected one - Remove mock data to prevent unauthorized API calls 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
127 lines
2.8 KiB
TypeScript
127 lines
2.8 KiB
TypeScript
/**
|
|
* Safe token storage utilities that work with both SSR and client-side rendering
|
|
*/
|
|
|
|
export const tokenStorage = {
|
|
/**
|
|
* Get access token from storage
|
|
*/
|
|
getAccessToken: (): string | null => {
|
|
if (typeof window === 'undefined') {
|
|
return null;
|
|
}
|
|
try {
|
|
return localStorage.getItem('accessToken');
|
|
} catch (error) {
|
|
console.error('Error reading accessToken:', error);
|
|
return null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Get refresh token from storage
|
|
*/
|
|
getRefreshToken: (): string | null => {
|
|
if (typeof window === 'undefined') {
|
|
return null;
|
|
}
|
|
try {
|
|
return localStorage.getItem('refreshToken');
|
|
} catch (error) {
|
|
console.error('Error reading refreshToken:', error);
|
|
return null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Set access token in storage
|
|
*/
|
|
setAccessToken: (token: string): void => {
|
|
if (typeof window === 'undefined') {
|
|
return;
|
|
}
|
|
try {
|
|
localStorage.setItem('accessToken', token);
|
|
} catch (error) {
|
|
console.error('Error setting accessToken:', error);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Set refresh token in storage
|
|
*/
|
|
setRefreshToken: (token: string): void => {
|
|
if (typeof window === 'undefined') {
|
|
return;
|
|
}
|
|
try {
|
|
localStorage.setItem('refreshToken', token);
|
|
} catch (error) {
|
|
console.error('Error setting refreshToken:', error);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Get device ID from storage
|
|
*/
|
|
getDeviceId: (): string | null => {
|
|
if (typeof window === 'undefined') {
|
|
return null;
|
|
}
|
|
try {
|
|
return localStorage.getItem('deviceId');
|
|
} catch (error) {
|
|
console.error('Error reading deviceId:', error);
|
|
return null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Set device ID in storage
|
|
*/
|
|
setDeviceId: (deviceId: string): void => {
|
|
if (typeof window === 'undefined') {
|
|
return;
|
|
}
|
|
try {
|
|
localStorage.setItem('deviceId', deviceId);
|
|
} catch (error) {
|
|
console.error('Error setting deviceId:', error);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Set both tokens at once
|
|
*/
|
|
setTokens: (accessToken: string, refreshToken: string, deviceId?: string): void => {
|
|
tokenStorage.setAccessToken(accessToken);
|
|
tokenStorage.setRefreshToken(refreshToken);
|
|
if (deviceId) {
|
|
tokenStorage.setDeviceId(deviceId);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Clear all tokens from storage
|
|
*/
|
|
clearTokens: (): void => {
|
|
if (typeof window === 'undefined') {
|
|
return;
|
|
}
|
|
try {
|
|
localStorage.removeItem('accessToken');
|
|
localStorage.removeItem('refreshToken');
|
|
localStorage.removeItem('deviceId');
|
|
} catch (error) {
|
|
console.error('Error clearing tokens:', error);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Check if user has valid tokens
|
|
*/
|
|
hasTokens: (): boolean => {
|
|
return !!(tokenStorage.getAccessToken() && tokenStorage.getRefreshToken());
|
|
},
|
|
};
|