🏷️ Search for custom tailnet name offers with keywords.
at master 123 lines 3.3 kB view raw
1import { useEffect, useState } from 'react'; 2import { 3 extractTailnetNameFromToken, 4 filterValidTokens, 5} from '$helpers/tokens'; 6import type { RuntimeMessage } from '$types/messages'; 7import type { Token } from '$types/tokens'; 8 9interface UseTokensOptions { 10 setLoading?: (v: boolean) => void; 11 setAlertMessage?: (v: string) => void; 12 setShowAlert?: (v: boolean) => void; 13 setClaimedToken?: (v: string) => void; 14} 15 16// Custom hook for managing tokens and claiming tokens 17export const useTokens = ({ 18 setLoading, 19 setAlertMessage, 20 setShowAlert, 21 setClaimedToken, 22}: UseTokensOptions = {}) => { 23 const [tokens, setTokens] = useState<Token[]>([]); 24 const [error, setError] = useState<string | null>(null); 25 26 // Load and check for expired tokens once on mount 27 useEffect(() => { 28 const checkExpiredTokens = () => { 29 browser.storage.local.get(['tailscaleTokens'], (result) => { 30 const now = Date.now(); 31 32 const validTokens: Token[] = filterValidTokens( 33 result.tailscaleTokens || [], 34 now, 35 ); 36 37 if (validTokens.length !== (result.tailscaleTokens || []).length) { 38 browser.storage.local.set({ tailscaleTokens: validTokens }); 39 } 40 41 setTokens(validTokens); 42 }); 43 }; 44 45 // Check immediately on mount 46 checkExpiredTokens(); 47 }, []); 48 49 useEffect(() => { 50 const listener = (message: RuntimeMessage<{ tokens?: Token[] }>) => { 51 if (message.action === 'tokensUpdated') { 52 const validTokens: Token[] = filterValidTokens(message.tokens || []); 53 setTokens(validTokens); 54 } 55 }; 56 browser.runtime.onMessage.addListener(listener); 57 return () => browser.runtime.onMessage.removeListener(listener); 58 }, []); 59 60 // Claim token logic 61 const handleClaimToken = async (tokenObj: Token) => { 62 if (setLoading) setLoading(true); 63 setError(null); 64 65 try { 66 const response = await browser.runtime.sendMessage({ 67 action: 'claimToken', 68 tcd: tokenObj.tcd || '', 69 token: tokenObj.token, 70 }); 71 if (response?.redirected) { 72 // DNS page opened, do not show error or alert 73 return; 74 } 75 76 if (response?.error) { 77 console.error(`[useTokens] Error claiming token:`, response.error); 78 if (setAlertMessage && setShowAlert) { 79 setAlertMessage(response.error); 80 setShowAlert(true); 81 } else { 82 setError(response.error); 83 } 84 return; 85 } 86 87 if (setAlertMessage && setShowAlert && setClaimedToken) { 88 setAlertMessage( 89 `Offer claimed! Your tailnet name is now ${extractTailnetNameFromToken( 90 tokenObj.token, 91 )}.\nYou may need to refresh Tailscale to see the changes take effect.`, 92 ); 93 setShowAlert(true); 94 setClaimedToken(tokenObj.token); 95 return; 96 } 97 } catch (e) { 98 const errorMsg = e instanceof Error ? e.message : 'Unknown error'; 99 100 if (setAlertMessage && setShowAlert) { 101 setAlertMessage(`Error claiming token: ${errorMsg}`); 102 setShowAlert(true); 103 } else { 104 setError(errorMsg); 105 } 106 } finally { 107 if (setLoading) setLoading(false); 108 } 109 }; 110 111 // Remove token logic 112 const handleRemoveToken = (tokenObj: Token) => { 113 const updatedTokens = tokens.filter((t) => t.token !== tokenObj.token); 114 setTokens(updatedTokens); 115 browser.storage.local.set({ tailscaleTokens: updatedTokens }); 116 }; 117 118 return [ 119 tokens, 120 setTokens, 121 { handleClaimToken, handleRemoveToken, error }, 122 ] as const; 123};