import type { RequestInstance, ResolvedQueueItemType, QueueDataType, QueueItemType } from "@hyper-fetch/core"; import { Request, getRequestDispatcher, scopeKey } from "@hyper-fetch/core"; import { useState, useEffect, useCallback } from "react"; import type { UseQueueOptionsType, QueueRequest, UseQueueReturnType } from "hooks/use-queue"; import { useQueueDefaultOptions } from "hooks/use-queue"; import { useProvider } from "provider"; const canUpdate = ( item: Pick, "success" | "failed" | "canceled" | "removed" | "resolved">, ) => { if (item.success || item.failed) { return false; } return true; }; /** * This hook allows to control dispatchers request queues * @param request * @param options * @returns */ export const useQueue = ( request: Request, options?: UseQueueOptionsType, ): UseQueueReturnType => { // Build the configuration options const { config: globalConfig } = useProvider(); const { dispatcherType, keepFinishedRequests } = { ...useQueueDefaultOptions, ...globalConfig.useQueueConfig, ...options, }; const { abortKey: rawAbortKey, queryKey: rawQueryKey, scope, client } = request; const abortKey = scopeKey(rawAbortKey, scope); const queryKey = scopeKey(rawQueryKey, scope); const { requestManager } = client; const [dispatcher] = getRequestDispatcher(request, dispatcherType); const [stopped, setStopped] = useState(false); const [requests, setRequests] = useState[]>([]); // ****************** // Mapping // ****************** const createRequestsArray = useCallback( ( queueElements: ResolvedQueueItemType[], prevRequests?: QueueRequest[], ): QueueRequest[] => { const newRequests = queueElements // Keep only unique requests .filter((el) => !prevRequests?.some((prevEl) => prevEl.requestId === el.requestId)) .map>((req) => ({ failed: false, canceled: false, removed: false, success: false, ...req, downloading: { progress: 0, timeLeft: 0, sizeLeft: 0, total: 0, loaded: 0, startTimestamp: 0, }, uploading: { progress: 0, timeLeft: 0, sizeLeft: 0, total: 0, loaded: 0, startTimestamp: 0, }, stopRequest: () => dispatcher.stopRequest(queryKey, req.requestId), startRequest: () => dispatcher.startRequest(queryKey, req.requestId), deleteRequest: () => dispatcher.delete(queryKey, req.requestId, abortKey), })); if (keepFinishedRequests && prevRequests) { return [...prevRequests, ...newRequests]; } return newRequests; }, [abortKey, dispatcher, queryKey, keepFinishedRequests], ); const mergePayloadType = useCallback((requestId: string, data: Partial>) => { setRequests((prev) => prev.map((el) => { if (el.requestId === requestId && canUpdate(el)) { return { ...el, ...data }; } return el; }), ); }, []); // ****************** // State // ****************** const getInitialState = () => { const requestQueue = dispatcher.getQueue(queryKey); setStopped(requestQueue.stopped); setRequests(createRequestsArray(requestQueue.requests)); }; const resolveQueueItems = useCallback( (items: QueueItemType[]): ResolvedQueueItemType[] => { return items.map((item) => { if (item.request instanceof Request) { return item as ResolvedQueueItemType; } return { ...item, request: client.fromJSON(item.request) as Request }; }); }, [client], ); const updateQueueState = useCallback( (values: QueueDataType) => { setStopped(values.stopped); setRequests((prev) => createRequestsArray(resolveQueueItems(values.requests), prev)); }, [createRequestsArray, resolveQueueItems], ); // ****************** // Events // ****************** const mountEvents = () => { const unmountChange = dispatcher.events.onQueueChangeByKey(queryKey, updateQueueState); const unmountStatus = dispatcher.events.onQueueStatusChangeByKey(queryKey, updateQueueState); const unmountFailed = requestManager.events.onResponse(({ requestId, response }) => { if (!response.success) { setRequests((prev) => prev.map((el) => (el.requestId === requestId ? { ...el, failed: true } : el))); } else { setRequests((prev) => prev.map((el) => (el.requestId === requestId ? { ...el, success: true } : el))); } }); const unmountCanceled = requestManager.events.onAbort(({ requestId }) => { setRequests((prev) => prev.map((el) => (el.requestId === requestId ? { ...el, canceled: true } : el))); }); const unmountRemoved = requestManager.events.onRemove(({ requestId }) => { setRequests((prev) => prev.map((el) => (el.requestId === requestId && canUpdate(el) ? { ...el, removed: true } : el)), ); }); const unmountDownload = requestManager.events.onDownloadProgress( ({ progress, timeLeft, sizeLeft, total, loaded, startTimestamp, requestId }) => { mergePayloadType(requestId, { downloading: { progress, timeLeft, sizeLeft, total, loaded, startTimestamp } }); }, ); const unmountUpload = requestManager.events.onUploadProgress( ({ progress, timeLeft, sizeLeft, total, loaded, startTimestamp, requestId }) => { mergePayloadType(requestId, { uploading: { progress, timeLeft, sizeLeft, total, loaded, startTimestamp } }); }, ); const unmount = () => { unmountStatus(); unmountChange(); unmountDownload(); unmountUpload(); unmountFailed(); unmountCanceled(); unmountRemoved(); }; return unmount; }; // ****************** // Lifecycle // ****************** useEffect(getInitialState, [createRequestsArray, dispatcher, queryKey]); useEffect(mountEvents, [ stopped, requests, setRequests, setStopped, queryKey, dispatcher.events, requestManager.events, updateQueueState, mergePayloadType, ]); return { stopped, requests, dispatcher, stop: () => dispatcher.stop(queryKey), pause: () => dispatcher.pause(queryKey), start: () => dispatcher.start(queryKey), }; };