From 714cd48feb4853ad225bdb295939647eb9f252ca Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Mon, 20 Jul 2026 11:57:07 +0530 Subject: [PATCH 1/2] Add online/offline connectivity callbacks and globalThis fixes --- src/core/reducer.ts | 4 ++-- src/core/useUploader.ts | 46 +++++++++++++++++++++++++++++++---------- src/types.ts | 2 ++ 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/core/reducer.ts b/src/core/reducer.ts index 4725e25..5d7df32 100644 --- a/src/core/reducer.ts +++ b/src/core/reducer.ts @@ -58,8 +58,8 @@ export function reducer(state: InternalState, action: Action): InternalState { case "SUCCESS": return { ...state, status: "success", progress: 100 }; case "ERROR": return { ...state, status: "error", error: action.error }; case "RESET": return { ...initialState, isOffline: state.isOffline }; - case "OFFLINE": return { ...state, isOffline: true }; - case "ONLINE": return { ...state, isOffline: false }; + case "OFFLINE": return state.isOffline ? state : { ...state, isOffline: true }; + case "ONLINE": return state.isOffline ? { ...state, isOffline: false } : state; default: return state; } } \ No newline at end of file diff --git a/src/core/useUploader.ts b/src/core/useUploader.ts index 1d5057f..5c1889d 100644 --- a/src/core/useUploader.ts +++ b/src/core/useUploader.ts @@ -16,10 +16,14 @@ export function useUploader(props: FastPixUploaderProps): UploaderContextValue { onFileSelect, onFileReject, onUploadStart, onProgress, onChunkAttempt, onChunkSuccess, onChunkAttemptFailure, onPause, onResume, onAbort, onError, onSuccess, onStateChange, + onOffline, onOnline, } = props; const [state, dispatch] = useReducer(reducer, initialState); + const netCbRef = useRef({ onOnline, onOffline }); + netCbRef.current = { onOnline, onOffline }; + const stateRef = useRef(state); stateRef.current = state; @@ -162,17 +166,6 @@ export function useUploader(props: FastPixUploaderProps): UploaderContextValue { }); }); - engine.on("offline", () => { - dispatch({ type: "OFFLINE" }); - }); - - engine.on("online", () => { - dispatch({ type: "ONLINE" }); - if (pauseIntentRef.current) { - try { engineRef.current?.pause(); } catch { /* noop */ } - } - }); - engine.on("success", () => { clearEngine(); dispatch({ type: "SUCCESS" }); @@ -181,6 +174,7 @@ export function useUploader(props: FastPixUploaderProps): UploaderContextValue { engine.on("error", (e: any) => { const message = e?.detail?.message ?? "Upload failed"; + try { engine.abort(); } catch { /* detach listeners, kill session */ } clearEngine(); dispatch({ type: "ERROR", error: { message } }); onError?.({ message }); @@ -233,6 +227,36 @@ export function useUploader(props: FastPixUploaderProps): UploaderContextValue { dispatch({ type: "RESET" }); }, [cancelEngine]); + useEffect(() => { + if (globalThis.window === undefined) return; + + const handleOffline = () => { + dispatch({ type: "OFFLINE" }); + netCbRef.current.onOffline?.(); + }; + + const handleOnline = () => { + dispatch({ type: "ONLINE" }); + if (pauseIntentRef.current) { + try { engineRef.current?.pause(); } catch { /* noop */ } + } + netCbRef.current.onOnline?.(); + }; + + // Sync initial connectivity without firing a transition callback. + if (globalThis.navigator !== undefined && !globalThis.navigator.onLine) { + dispatch({ type: "OFFLINE" }); + } + + globalThis.window.addEventListener("offline", handleOffline); + globalThis.window.addEventListener("online", handleOnline); + + return () => { + globalThis.window.removeEventListener("offline", handleOffline); + globalThis.window.removeEventListener("online", handleOnline); + }; +}, []); + useEffect(() => { if (fileProp) selectFile(fileProp); }, [fileProp]); diff --git a/src/types.ts b/src/types.ts index 90d43de..25a5faf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -52,6 +52,8 @@ export interface FastPixUploaderProps { onError?: (error: UploaderError) => void; onSuccess?: () => void; onStateChange?: (state: UploaderState) => void; + onOffline?: () => void; + onOnline?: () => void; } export interface FastPixUploaderRef { From 89d16db8593d8219632a1a79458902e7f20bdd42 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Tue, 28 Jul 2026 16:13:18 +0530 Subject: [PATCH 2/2] fix: drop pauseIntentRef; autostart on reselect after error --- src/core/useUploader.ts | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/src/core/useUploader.ts b/src/core/useUploader.ts index 5c1889d..c96756d 100644 --- a/src/core/useUploader.ts +++ b/src/core/useUploader.ts @@ -79,8 +79,9 @@ export function useUploader(props: FastPixUploaderProps): UploaderContextValue { return; } - dispatch({ type: "SELECT_FILE", file: f }); - onFileSelect?.(f); + autoStartedFor.current = null; + dispatch({ type: "SELECT_FILE", file: f }); + onFileSelect?.(f); }, [disabled, accept, maxFileSize, onFileReject, onFileSelect]); const start = useCallback(async () => { @@ -128,12 +129,7 @@ export function useUploader(props: FastPixUploaderProps): UploaderContextValue { engineRef.current = engine; engine.on("progress", (e: any) => { - if (stateRef.current.status === "paused") { - if (pauseIntentRef.current) { - try { engineRef.current?.pause(); } catch { /* noop */ } - } - return; - } + if (stateRef.current.status === "paused") return; const value = Math.round(e?.detail?.progress ?? 0); dispatch({ type: "PROGRESS", value }); onProgress?.(value); @@ -194,11 +190,8 @@ export function useUploader(props: FastPixUploaderProps): UploaderContextValue { const startRef = useRef(start); startRef.current = start; - const pauseIntentRef = useRef(false); - const pause = useCallback(() => { if (stateRef.current.status !== "uploading") return; - pauseIntentRef.current = true; try { engineRef.current?.pause(); } catch { /* noop */ } dispatch({ type: "PAUSE" }); onPause?.(); @@ -206,14 +199,12 @@ export function useUploader(props: FastPixUploaderProps): UploaderContextValue { const resume = useCallback(async () => { if (stateRef.current.status !== "paused") return; - pauseIntentRef.current = false; try { await engineRef.current?.resume(); } catch { /* noop */ } dispatch({ type: "RESUME" }); onResume?.(); }, [onResume]); const abort = useCallback(() => { - pauseIntentRef.current = false; autoStartedFor.current = null; cancelEngine(); dispatch({ type: "RESET" }); @@ -221,7 +212,6 @@ export function useUploader(props: FastPixUploaderProps): UploaderContextValue { }, [cancelEngine, onAbort]); const reset = useCallback(() => { - pauseIntentRef.current = false; autoStartedFor.current = null; cancelEngine(); dispatch({ type: "RESET" }); @@ -235,13 +225,10 @@ export function useUploader(props: FastPixUploaderProps): UploaderContextValue { netCbRef.current.onOffline?.(); }; - const handleOnline = () => { - dispatch({ type: "ONLINE" }); - if (pauseIntentRef.current) { - try { engineRef.current?.pause(); } catch { /* noop */ } - } - netCbRef.current.onOnline?.(); - }; + const handleOnline = () => { + dispatch({ type: "ONLINE" }); + netCbRef.current.onOnline?.(); + }; // Sync initial connectivity without firing a transition callback. if (globalThis.navigator !== undefined && !globalThis.navigator.onLine) {