forked from Remitwise-Org/Remitwise-Frontend
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsessionHandler.ts
More file actions
184 lines (165 loc) · 5.53 KB
/
Copy pathsessionHandler.ts
File metadata and controls
184 lines (165 loc) · 5.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/**
* Frontend session expiry handler
* Detects session expiry from API responses and manages user flow
*
* @example Usage in API client wrapper
* ```typescript
* import { sessionHandler } from '@/lib/client/sessionHandler';
*
* async function apiRequest(url: string, options?: RequestInit) {
* const response = await fetch(url, options);
*
* if (sessionHandler.isSessionExpired(response)) {
* sessionHandler.handleSessionExpiry(window.location.pathname);
* return null;
* }
*
* return response;
* }
* ```
*/
export interface SessionHandler {
/**
* Check if response indicates session expiry
* @param response - The fetch Response object to check
* @returns true if response is 401 with "Session expired" message
*/
isSessionExpired(response: Response): Promise<boolean>;
/**
* Handle session expiry flow
* Clears local auth state, shows message, and redirects to wallet connection
* @param intendedPath - Optional path to redirect to after re-authentication
*/
handleSessionExpiry(intendedPath?: string): void;
/**
* Dispatch session-expiring warning event
* Call this when the backend indicates the session is about to expire
* @param countdown - Seconds remaining before expiry (default 120)
* @param message - Optional custom message
*/
dispatchSessionExpiring(countdown?: number, message?: string): void;
/**
* Attempt to refresh the session
* @returns true if session was refreshed, false otherwise
*/
refreshSession(): Promise<boolean>;
/**
* Clear local authentication state
* Removes stored wallet address and connection status
*/
clearAuthState(): void;
}
// Store the active refresh promise to deduplicate concurrent requests
let refreshPromise: Promise<boolean> | null = null;
/**
* Check if a response indicates session expiry
* @param response - The fetch Response object to check
* @returns true if response is 401 with "Session expired" message
*/
async function isSessionExpired(response: Response): Promise<boolean> {
if (response.status !== 401) {
return false;
}
try {
// Clone the response so the original can still be consumed
const cloned = response.clone();
const data = await cloned.json();
return data.message === 'Session expired';
} catch {
// If we can't parse JSON, it's not a session expiry response
return false;
}
}
/**
* Attempt to refresh the current session by calling the refresh endpoint
* Deduplicates concurrent calls to ensure only one refresh request is made at a time.
* @returns true if session was refreshed, false otherwise
*/
async function refreshSession(): Promise<boolean> {
if (refreshPromise) {
return refreshPromise;
}
refreshPromise = (async () => {
try {
const response = await fetch('/api/auth/refresh', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});
return response.ok;
} catch {
return false;
} finally {
refreshPromise = null;
}
})();
return refreshPromise;
}
/**
* Dispatch session-expiring warning event
* Call this when the backend indicates the session is about to expire
* @param countdown - Seconds remaining before expiry (default 120)
* @param message - Optional custom message
*/
function dispatchSessionExpiring(countdown: number = 120, message?: string): void {
if (typeof window === 'undefined') return;
const event = new CustomEvent('session-expiring', {
detail: {
message: message || `Your session will expire in ${countdown} seconds. For your security, you'll be signed out automatically.`,
countdown,
},
});
window.dispatchEvent(event);
}
/**
* Clear local authentication state
* Removes stored wallet address and connection status from localStorage
*/
function clearAuthState(): void {
// Clear any stored authentication data
if (typeof window !== 'undefined') {
localStorage.removeItem('wallet_address');
localStorage.removeItem('wallet_connected');
localStorage.removeItem('auth_state');
localStorage.removeItem('remitwise_session_expiry');
}
}
/**
* Handle session expiry flow
* Clears local state, displays message, and redirects to wallet connection
* @param intendedPath - Optional path to redirect to after re-authentication
*/
function handleSessionExpiry(intendedPath?: string): void {
if (typeof window === 'undefined') return;
// Clear local authentication state
clearAuthState();
// Store intended destination for post-auth redirect
// Preserve the user's intended destination so they can be redirected back after re-authentication
if (intendedPath && intendedPath !== '/') {
localStorage.setItem('redirect_after_auth', intendedPath);
}
// Trigger a custom event that can be listened to by UI components
// This allows for flexible notification handling (toast, modal, etc.)
const event = new CustomEvent('session-expired', {
detail: { message: 'Your session has expired. Please reconnect your wallet.' }
});
window.dispatchEvent(event);
// Redirect to wallet connection page (home page)
// Delay gives the user time to see the expired notification and optionally
// click "Reconnect wallet" before the auto-redirect fires.
setTimeout(() => {
window.location.href = '/';
}, 15000);
}
/**
* Session handler instance
* Use this singleton to handle session expiry across your application
*/
export const sessionHandler: SessionHandler = {
isSessionExpired,
refreshSession,
handleSessionExpiry,
dispatchSessionExpiring,
clearAuthState,
};