From 714cd48feb4853ad225bdb295939647eb9f252ca Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Mon, 20 Jul 2026 11:57:07 +0530 Subject: [PATCH] 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 {