import { type FormEvent, useCallback, useEffect, useState } from "react"; import { P, match } from "ts-pattern"; import packageJson from "../package.json"; import "./style.css"; import { sendToBackground, sendToContentScript } from "@plasmohq/messaging"; import { AUTH_FACTOR_TOKEN_REQUIRED_ERROR_MESSAGE, BSKY_DOMAIN, DOCUMENT_LINK, MAX_RELOAD_COUNT, MESSAGE_NAMES, MESSAGE_TYPE, RATE_LIMIT_ERROR_MESSAGE, STORAGE_KEYS, TARGET_URLS_REGEX, } from "~lib/constants"; function IndexPopup() { const [isLoading, setIsLoading] = useState(false); const [password, setPassword] = useState(""); const [identifier, setIdentifier] = useState(""); const [reloadCount, setReloadCount] = useState(0); const [authFactorToken, setAuthFactorToken] = useState(""); const [isShowAuthFactorTokenInput, setIsShowAuthFactorTokenInput] = useState(false); const [message, setMessage] = useState(null); const isShowErrorMessage = message?.type === MESSAGE_TYPE.ERROR; const isShowSuccessMessage = message?.type === MESSAGE_TYPE.SUCCESS; const setErrorMessage = (message: string, documentLink?: string) => { setMessage({ type: MESSAGE_TYPE.ERROR, message, documentLink }); }; const reloadActiveTab = async () => { const [{ id: tabId }] = await chrome.tabs.query({ active: true, currentWindow: true, }); await chrome.tabs.reload(tabId); }; const saveCredentialsToStorage = async () => { await chrome.storage.local.set({ [STORAGE_KEYS.BSKY_USER_ID]: identifier, [STORAGE_KEYS.BSKY_PASSWORD]: password, }); }; const clearPasswordFromStorage = async () => { await chrome.storage.local.remove([STORAGE_KEYS.BSKY_PASSWORD]); }; const saveShowAuthFactorTokenInputToStorage = async (value: boolean) => { await chrome.storage.local.set({ [STORAGE_KEYS.BSKY_SHOW_AUTH_FACTOR_TOKEN_INPUT]: value, }); }; const loadCredentialsFromStorage = useCallback(async () => { chrome.storage.local.get( [ STORAGE_KEYS.BSKY_USER_ID, STORAGE_KEYS.BSKY_PASSWORD, STORAGE_KEYS.BSKY_SHOW_AUTH_FACTOR_TOKEN_INPUT, ], (result) => { setIdentifier(result[STORAGE_KEYS.BSKY_USER_ID] || ""); setPassword(result[STORAGE_KEYS.BSKY_PASSWORD] || ""); setIsShowAuthFactorTokenInput( result[STORAGE_KEYS.BSKY_SHOW_AUTH_FACTOR_TOKEN_INPUT] || false, ); }, ); }, []); const validateForm = () => { if (!password && !identifier) { setErrorMessage("Error: Please enter your password and identifier."); return false; } if (!password) { setErrorMessage("Error: Please enter your password."); return false; } if (!identifier) { setErrorMessage("Error: Please enter your identifier."); return false; } return true; }; const searchBskyUser = async (e?: FormEvent) => { if (e) { e.preventDefault(); } if (!validateForm()) { return; } saveCredentialsToStorage(); const [{ url: currentUrl }] = await chrome.tabs.query({ active: true, currentWindow: true, }); if (!Object.values(TARGET_URLS_REGEX).some((r) => r.test(currentUrl))) { setErrorMessage( "Error: Invalid page. please open the 𝕏 following or blocking or list page.", DOCUMENT_LINK.PAGE_ERROR, ); return; } const messageName = match(currentUrl) .with( P.when((url) => TARGET_URLS_REGEX.FOLLOW.test(url)), () => MESSAGE_NAMES.SEARCH_BSKY_USER_ON_FOLLOW_PAGE, ) .with( P.when((url) => TARGET_URLS_REGEX.BLOCK.test(url)), () => MESSAGE_NAMES.SEARCH_BSKY_USER_ON_BLOCK_PAGE, ) .with( P.when((url) => TARGET_URLS_REGEX.LIST.test(url)), () => MESSAGE_NAMES.SEARCH_BSKY_USER_ON_LIST_MEMBERS_PAGE, ) .run(); await chrome.storage.local.set({ [STORAGE_KEYS.BSKY_MESSAGE_NAME]: messageName, }); setMessage(null); setIsLoading(true); const formattedIdentifier = ( identifier.includes(".") ? identifier : `${identifier}.${BSKY_DOMAIN}` ).replace(/^@/, ""); try { const { session, error } = await sendToBackground({ name: "login", body: { identifier: formattedIdentifier, password, ...(authFactorToken && { authFactorToken: authFactorToken }), }, }); if (error) { if (error.message.includes(AUTH_FACTOR_TOKEN_REQUIRED_ERROR_MESSAGE)) { setIsShowAuthFactorTokenInput(true); await saveShowAuthFactorTokenInputToStorage(true); } else if (error.message.includes(RATE_LIMIT_ERROR_MESSAGE)) { setErrorMessage(error.message, DOCUMENT_LINK.RATE_LIMIT_ERROR); } else { setErrorMessage(error.message, DOCUMENT_LINK.LOGIN_ERROR); } return; } await chrome.storage.local.set({ [STORAGE_KEYS.BSKY_CLIENT_SESSION]: session, }); await clearPasswordFromStorage(); await sendToContentScript({ name: messageName, }); await saveShowAuthFactorTokenInputToStorage(false); window.close(); } catch (e) { if ( e.message?.includes("Could not establish connection") && reloadCount < MAX_RELOAD_COUNT ) { setReloadCount((prev) => prev + 1); await reloadActiveTab(); await new Promise((r) => setTimeout(r, 3000)); await searchBskyUser(); } else { setErrorMessage( "Error: Something went wrong. Please reload the web page and try again.", DOCUMENT_LINK.OTHER_ERROR, ); } } finally { setIsLoading(false); } }; useEffect(() => { loadCredentialsFromStorage(); }, [loadCredentialsFromStorage]); return (

Sky Follower Bridge{" "} v{packageJson.version}

{isShowAuthFactorTokenInput && ( )} {isShowErrorMessage && (
{message.message} {message.documentLink && ( Learn more )} .
)} {isShowSuccessMessage && (
Success. Try again if no results found.
)}
); } export default IndexPopup;