import { useState, useEffect } from 'react'; import { toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import type { LoadingState } from 'web-check-live/components/misc/ProgressBar'; import type { AddressType } from 'web-check-live/utils/address-type-checker'; import keys from 'web-check-live/utils/get-keys'; interface UseIpAddressProps { // Unique identifier for this job type jobId: string | string[]; // The actual fetch request fetchRequest: () => Promise; // Function to call to update the loading state in parent updateLoadingJobs: (job: string | string[], newState: LoadingState, error?: string, retry?: (data?: any) => void | null, data?: any) => void; addressInfo: { // The hostname/ip address that we're checking address: string | undefined; // The type of address (e.g. url, ipv4) addressType: AddressType; // The valid address types for this job expectedAddressTypes: AddressType[]; }; } type ResultType = any; type ReturnType = [ResultType | undefined, (data?: any) => void]; const useMotherOfAllHooks = (params: UseIpAddressProps): ReturnType => { // Destructure params const { addressInfo, fetchRequest, jobId, updateLoadingJobs } = params; const { address, addressType, expectedAddressTypes } = addressInfo; // Build useState that will be returned const [result, setResult] = useState(); // Fire off the HTTP fetch request, then set results and update loading / error state const doTheFetch = () => { if (keys.disableEverything) { updateLoadingJobs(jobId, 'skipped', 'Web-Check is temporarily disabled. Please try again later.', reset); return Promise.resolve(); } return fetchRequest() .then((res: any) => { if (!res) { // No response :( updateLoadingJobs(jobId, 'error', 'No response', reset); } else if (res.error) { // Response returned an error message if (res.error.includes("timed-out")) { // Specific handling for timeout errors updateLoadingJobs(jobId, 'timed-out', res.error, reset); } else { updateLoadingJobs(jobId, 'error', res.error, reset); } } else if (res.errorType && res.errorMessage) { const errorMessage = `${res.errorType}\n${res.errorMessage}\n\n` + `This sometimes occurs on Netlify if using the free plan. You may need to upgrade to use lambda functions`; updateLoadingJobs(jobId, 'error', errorMessage, reset); } else if (res.skipped) { // Response returned a skipped message updateLoadingJobs(jobId, 'skipped', res.skipped, reset); } else { // Yay, everything went to plan :) setResult(res); updateLoadingJobs(jobId, 'success', '', undefined, res); } }) .catch((err) => { // Something fucked up updateLoadingJobs(jobId, 'error', err.error || err.message || 'Unknown error', reset); throw err; }) } // For when the user manually re-triggers the job const reset = (data: any) => { // If data is provided, then update state if (data && !(data instanceof Event) && !data?._reactName) { setResult(data); } else { // Otherwise, trigger a data re-fetch updateLoadingJobs(jobId, 'loading'); const fetchyFetch = doTheFetch(); const toastOptions = { pending: `Updating Data (${jobId})`, success: `Completed (${jobId})`, error: `Failed to update (${jobId})`, skipped: `Skipped job (${jobId}), as no valid results for host`, }; // Initiate fetch, and show progress toast toast.promise(fetchyFetch, toastOptions).catch(() => {}); } }; useEffect(() => { // Still waiting for this upstream, cancel job if (!address || !addressType) { return; } // This job isn't needed for this address type, cancel job if (!expectedAddressTypes.includes(addressType)) { if (addressType !== 'empt') updateLoadingJobs(jobId, 'skipped'); return; } // Initiate the data fetching process doTheFetch().catch(() => {}); // eslint-disable-next-line react-hooks/exhaustive-deps }, [address, addressType]); return [result, reset]; }; export default useMotherOfAllHooks; // I really fucking hate TypeScript sometimes.... // Feels like a weak attempt at trying to make JavaScript less crappy, // when the real solution would be to just switch to a proper, typed, safe language // ... Either that, or I'm just really shit at it.