diff --git a/browser-extensions/chrome/src/App.tsx b/browser-extensions/chrome/src/App.tsx index 0540b1f03..2d508b845 100644 --- a/browser-extensions/chrome/src/App.tsx +++ b/browser-extensions/chrome/src/App.tsx @@ -11,7 +11,6 @@ import './styles/app.css'; import EncryptionUtility from './utils/EncryptionUtility'; import { VaultResponse } from './types/webapi/VaultResponse'; import { useWebApi } from './context/WebApiContext'; -import SrpUtility from './utils/SrpUtility'; /** * Main application component @@ -76,11 +75,12 @@ const App: React.FC = () => { */ const handleLogout = async (): Promise => { setIsLoading(true); + setIsUserMenuOpen(false); try { + await webApi.logout(); await authContext.logout(); } finally { setIsLoading(false); - setIsUserMenuOpen(false); } }; diff --git a/browser-extensions/chrome/src/contentScript.ts b/browser-extensions/chrome/src/contentScript.ts index 0ca42220c..25839aca2 100644 --- a/browser-extensions/chrome/src/contentScript.ts +++ b/browser-extensions/chrome/src/contentScript.ts @@ -46,15 +46,20 @@ function createPopup(input: HTMLInputElement, credentials: Credential[]) : void padding: 8px 0; `; - // Change to mousedown event instead of click - const handleClickOutside = (event: MouseEvent) => { + /** + * Close autofill popup when clicking outside. + */ + const handleClickOutside = (event: MouseEvent) : void => { if (!popup.contains(event.target as Node)) { removeExistingPopup(); document.removeEventListener('mousedown', handleClickOutside); } }; - // Use setTimeout to prevent immediate trigger of the mousedown event + /** + * Add event listener to document to close popup when clicking outside + * after a short delay to prevent immediate trigger of the mousedown event. + */ setTimeout(() => { document.addEventListener('mousedown', handleClickOutside); }, 100); diff --git a/browser-extensions/chrome/src/pages/Unlock.tsx b/browser-extensions/chrome/src/pages/Unlock.tsx index 653b29679..0910aac35 100644 --- a/browser-extensions/chrome/src/pages/Unlock.tsx +++ b/browser-extensions/chrome/src/pages/Unlock.tsx @@ -67,6 +67,7 @@ const Unlock: React.FC = () => { const handleLogout = async () : Promise => { showLoading(); try { + await webApi.logout(); await authContext.logout(); } finally { hideLoading(); diff --git a/browser-extensions/chrome/src/utils/WebApiService.ts b/browser-extensions/chrome/src/utils/WebApiService.ts index 27d035ca7..79c9e78ff 100644 --- a/browser-extensions/chrome/src/utils/WebApiService.ts +++ b/browser-extensions/chrome/src/utils/WebApiService.ts @@ -42,11 +42,12 @@ export class WebApiService { } /** - * Fetch data from the API + * Fetch data from the API. */ public async fetch( endpoint: string, - options: RequestInit = {} + options: RequestInit = {}, + parseJson: boolean = true ): Promise { const url = this.baseUrl + endpoint; const headers = new Headers(options.headers || {}); @@ -78,7 +79,7 @@ export class WebApiService { throw new Error('Request failed after token refresh'); } - return retryResponse.json(); + return parseJson ? retryResponse.json() : retryResponse.text() as T; } else { this.handleLogout(); throw new Error('Session expired'); @@ -89,7 +90,7 @@ export class WebApiService { throw new Error(`HTTP error! status: ${response.status}`); } - return response.json(); + return parseJson ? response.json() : response.text() as T; } catch (error) { console.error('API request failed:', error); throw error; @@ -97,7 +98,7 @@ export class WebApiService { } /** - * Refresh the access token + * Refresh the access token. */ private async refreshAccessToken(): Promise { const refreshToken = this.getRefreshToken(); @@ -132,27 +133,31 @@ export class WebApiService { } /** - * Get a resource + * Issue GET request to the API. */ public async get(endpoint: string): Promise { return this.fetch(endpoint, { method: 'GET' }); } /** - * Create a resource + * Issue POST request to the API. */ - public async post(endpoint: string, data: TRequest): Promise { + public async post( + endpoint: string, + data: TRequest, + parseJson: boolean = true + ): Promise { return this.fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), - }); + }, parseJson); } /** - * Update a resource + * Issue PUT request to the API. */ public async put(endpoint: string, data: TRequest): Promise { return this.fetch(endpoint, { @@ -165,9 +170,24 @@ export class WebApiService { } /** - * Delete a resource + * Issue DELETE request to the API. */ public async delete(endpoint: string): Promise { return this.fetch(endpoint, { method: 'DELETE' }); } -} \ No newline at end of file + + /** + * Logout and revoke tokens via WebApi. + */ + public async logout(): Promise { + const refreshToken = this.getRefreshToken(); + if (!refreshToken) { + return; + } + + await this.post('Auth/revoke', { + token: this.getAccessToken(), + refreshToken: refreshToken, + }, false); + } +}