@vibehooks/react 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +55 -0
- package/dist/useAsyncState.d.ts +52 -0
- package/dist/useAsyncState.js +173 -0
- package/dist/useAudio.d.ts +26 -0
- package/dist/useAudio.js +64 -0
- package/dist/useAutoScroll.d.ts +47 -0
- package/dist/useAutoScroll.js +122 -0
- package/dist/useBarcode.d.ts +77 -0
- package/dist/useBarcode.js +140 -0
- package/dist/useBatteryStatus.d.ts +53 -0
- package/dist/useBatteryStatus.js +67 -0
- package/dist/useBodyScrollFreeze.d.ts +36 -0
- package/dist/useBodyScrollFreeze.js +74 -0
- package/dist/useCameraCapture.d.ts +76 -0
- package/dist/useCameraCapture.js +116 -0
- package/dist/useCookies.d.ts +42 -0
- package/dist/useCookies.js +61 -0
- package/dist/useCopyToClipboard.d.ts +22 -0
- package/dist/useCopyToClipboard.js +31 -0
- package/dist/useCountDown.d.ts +80 -0
- package/dist/useCountDown.js +106 -0
- package/dist/useDebouncedState.d.ts +47 -0
- package/dist/useDebouncedState.js +47 -0
- package/dist/useExternalNotifications.d.ts +36 -0
- package/dist/useExternalNotifications.js +100 -0
- package/dist/useFile.d.ts +74 -0
- package/dist/useFile.js +74 -0
- package/dist/useFullScreen.d.ts +20 -0
- package/dist/useFullScreen.js +43 -0
- package/dist/useGeolocation.d.ts +47 -0
- package/dist/useGeolocation.js +68 -0
- package/dist/useHoverIntent.d.ts +45 -0
- package/dist/useHoverIntent.js +81 -0
- package/dist/useIdle.d.ts +47 -0
- package/dist/useIdle.js +59 -0
- package/dist/useIndexedDB.d.ts +60 -0
- package/dist/useIndexedDB.js +75 -0
- package/dist/useIntersectionObserver.d.ts +45 -0
- package/dist/useIntersectionObserver.js +70 -0
- package/dist/useIntervalSafe.d.ts +72 -0
- package/dist/useIntervalSafe.js +85 -0
- package/dist/useIsClient.d.ts +12 -0
- package/dist/useIsClient.js +21 -0
- package/dist/useIsDesktop.d.ts +12 -0
- package/dist/useIsDesktop.js +23 -0
- package/dist/useIsFirstRender.d.ts +12 -0
- package/dist/useIsFirstRender.js +21 -0
- package/dist/useList.d.ts +19 -0
- package/dist/useList.js +44 -0
- package/dist/useLocalNotifications.d.ts +23 -0
- package/dist/useLocalNotifications.js +50 -0
- package/dist/useLocalStorage.d.ts +45 -0
- package/dist/useLocalStorage.js +71 -0
- package/dist/useNetworkInformation.d.ts +138 -0
- package/dist/useNetworkInformation.js +76 -0
- package/dist/useOnline.d.ts +17 -0
- package/dist/useOnline.js +29 -0
- package/dist/usePageVisibility.d.ts +32 -0
- package/dist/usePageVisibility.js +65 -0
- package/dist/usePermissions.d.ts +28 -0
- package/dist/usePermissions.js +70 -0
- package/dist/usePictureInPicture.d.ts +47 -0
- package/dist/usePictureInPicture.js +60 -0
- package/dist/usePopover.d.ts +54 -0
- package/dist/usePopover.js +67 -0
- package/dist/usePreferredLanguage.d.ts +55 -0
- package/dist/usePreferredLanguage.js +127 -0
- package/dist/usePreferredTheme.d.ts +67 -0
- package/dist/usePreferredTheme.js +133 -0
- package/dist/usePreviousDistinct.d.ts +12 -0
- package/dist/usePreviousDistinct.js +23 -0
- package/dist/useResettableState.d.ts +15 -0
- package/dist/useResettableState.js +25 -0
- package/dist/useScreenOrientation.d.ts +48 -0
- package/dist/useScreenOrientation.js +51 -0
- package/dist/useScreenSize.d.ts +16 -0
- package/dist/useScreenSize.js +34 -0
- package/dist/useScreenWakeLock.d.ts +37 -0
- package/dist/useScreenWakeLock.js +48 -0
- package/dist/useServerSentEvent.d.ts +57 -0
- package/dist/useServerSentEvent.js +78 -0
- package/dist/useShoppingCart.d.ts +54 -0
- package/dist/useShoppingCart.js +122 -0
- package/dist/useSmartVideo.d.ts +35 -0
- package/dist/useSmartVideo.js +76 -0
- package/dist/useSpeech.d.ts +74 -0
- package/dist/useSpeech.js +156 -0
- package/dist/useSummarizer.d.ts +92 -0
- package/dist/useSummarizer.js +83 -0
- package/dist/useTaskQueue.d.ts +25 -0
- package/dist/useTaskQueue.js +51 -0
- package/dist/useThrottledCallback.d.ts +32 -0
- package/dist/useThrottledCallback.js +42 -0
- package/dist/useTimeout.d.ts +58 -0
- package/dist/useTimeout.js +70 -0
- package/dist/useToggle.d.ts +30 -0
- package/dist/useToggle.js +23 -0
- package/dist/useTraceUpdates.d.ts +22 -0
- package/dist/useTraceUpdates.js +38 -0
- package/dist/useTranslator.d.ts +110 -0
- package/dist/useTranslator.js +119 -0
- package/dist/useUserActivation.d.ts +40 -0
- package/dist/useUserActivation.js +63 -0
- package/dist/useVibration.d.ts +55 -0
- package/dist/useVibration.js +50 -0
- package/dist/useWebsocket.d.ts +80 -0
- package/dist/useWebsocket.js +125 -0
- package/package.json +70 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/useTranslator.ts
|
|
4
|
+
/**
|
|
5
|
+
* `useTranslator` is a React custom hook that provides a simple way to translate text using Translator API in Chrome to translate text with AI models provided in the browser.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* const {
|
|
10
|
+
* isSupported,
|
|
11
|
+
* translate,
|
|
12
|
+
* checkLanguageSupport,
|
|
13
|
+
* isLanguagePairSupported,
|
|
14
|
+
* isTranslating,
|
|
15
|
+
* translation,
|
|
16
|
+
* error,
|
|
17
|
+
* } = useTranslator({
|
|
18
|
+
* sourceLanguage: "es",
|
|
19
|
+
* targetLanguage: "en-US",
|
|
20
|
+
* onLanguageSupportCheck: (supported) => {
|
|
21
|
+
* console.log("Language pair supported:", supported);
|
|
22
|
+
* },
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* React.useEffect(() => {
|
|
26
|
+
* checkLanguageSupport();
|
|
27
|
+
* }, [checkLanguageSupport]);
|
|
28
|
+
*
|
|
29
|
+
* const handleTranslate = async () => {
|
|
30
|
+
* await translate("Hola mundo");
|
|
31
|
+
* };
|
|
32
|
+
* ```
|
|
33
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Translator
|
|
34
|
+
*/
|
|
35
|
+
function useTranslator(options) {
|
|
36
|
+
const { onLanguageSupportCheck, sourceLanguage, targetLanguage } = options;
|
|
37
|
+
const translatorRef = React.useRef(null);
|
|
38
|
+
const [isSupported, setIsSupported] = React.useState(true);
|
|
39
|
+
const [isLanguagePairSupported, setIsLanguagePairSupported] = React.useState(null);
|
|
40
|
+
const [isTranslating, setIsTranslating] = React.useState(false);
|
|
41
|
+
const [translation, setTranslation] = React.useState(null);
|
|
42
|
+
const [error, setError] = React.useState(null);
|
|
43
|
+
React.useEffect(() => {
|
|
44
|
+
if (typeof window === "undefined" || !window.Translator) {
|
|
45
|
+
setIsSupported(false);
|
|
46
|
+
setError(/* @__PURE__ */ new Error("Translator API is not supported in this browser."));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
setIsSupported(true);
|
|
50
|
+
}, []);
|
|
51
|
+
const checkLanguageSupport = React.useCallback(async () => {
|
|
52
|
+
if (!window.Translator) {
|
|
53
|
+
const error$1 = /* @__PURE__ */ new Error("Translator API is not supported in this browser.");
|
|
54
|
+
setError(error$1);
|
|
55
|
+
throw error$1;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const supported = await window.Translator.availability({
|
|
59
|
+
sourceLanguage,
|
|
60
|
+
targetLanguage
|
|
61
|
+
}) === "available";
|
|
62
|
+
setIsLanguagePairSupported(supported);
|
|
63
|
+
onLanguageSupportCheck?.(supported);
|
|
64
|
+
return supported;
|
|
65
|
+
} catch (err) {
|
|
66
|
+
const error$1 = err instanceof Error ? err : /* @__PURE__ */ new Error("Failed to check language support.");
|
|
67
|
+
setError(error$1);
|
|
68
|
+
throw error$1;
|
|
69
|
+
}
|
|
70
|
+
}, [
|
|
71
|
+
sourceLanguage,
|
|
72
|
+
targetLanguage,
|
|
73
|
+
onLanguageSupportCheck
|
|
74
|
+
]);
|
|
75
|
+
const ensureTranslator = React.useCallback(async () => {
|
|
76
|
+
if (translatorRef.current) return translatorRef.current;
|
|
77
|
+
if (!window.Translator) throw new Error("Translator API is not supported in this browser.");
|
|
78
|
+
const translator = await window.Translator.create({
|
|
79
|
+
sourceLanguage,
|
|
80
|
+
targetLanguage
|
|
81
|
+
});
|
|
82
|
+
translatorRef.current = translator;
|
|
83
|
+
return translator;
|
|
84
|
+
}, [sourceLanguage, targetLanguage]);
|
|
85
|
+
const translate = React.useCallback(async (text) => {
|
|
86
|
+
setIsTranslating(true);
|
|
87
|
+
setError(null);
|
|
88
|
+
try {
|
|
89
|
+
if (isLanguagePairSupported === false) throw new Error("The selected language pair is not supported.");
|
|
90
|
+
const result = await (await ensureTranslator()).translate(text);
|
|
91
|
+
setTranslation(result);
|
|
92
|
+
return result;
|
|
93
|
+
} catch (err) {
|
|
94
|
+
const error$1 = err instanceof Error ? err : /* @__PURE__ */ new Error("Failed to translate text.");
|
|
95
|
+
setError(error$1);
|
|
96
|
+
throw error$1;
|
|
97
|
+
} finally {
|
|
98
|
+
setIsTranslating(false);
|
|
99
|
+
}
|
|
100
|
+
}, [isLanguagePairSupported, ensureTranslator]);
|
|
101
|
+
React.useEffect(() => {
|
|
102
|
+
return () => {
|
|
103
|
+
translatorRef.current?.destroy();
|
|
104
|
+
translatorRef.current = null;
|
|
105
|
+
};
|
|
106
|
+
}, []);
|
|
107
|
+
return {
|
|
108
|
+
checkLanguageSupport,
|
|
109
|
+
error,
|
|
110
|
+
isLanguagePairSupported,
|
|
111
|
+
isSupported,
|
|
112
|
+
isTranslating,
|
|
113
|
+
translate,
|
|
114
|
+
translation
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
//#endregion
|
|
119
|
+
export { useTranslator };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region src/useUserActivation.d.ts
|
|
2
|
+
interface UseUserActivationReturn {
|
|
3
|
+
/**
|
|
4
|
+
* Whether the user has ever interacted with the document.
|
|
5
|
+
*/
|
|
6
|
+
hasBeenActive: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Whether there is a current transient user activation.
|
|
9
|
+
*/
|
|
10
|
+
isActive: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Indicates whether the UserActivation API is supported.
|
|
13
|
+
*/
|
|
14
|
+
isSupported: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Forces a re-read of the current activation state.
|
|
17
|
+
*/
|
|
18
|
+
refresh: () => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* `useUserActivation` is a React hook that exposes the browser User Activation State.
|
|
22
|
+
* This hook provides read-only access to `navigator.userActivation` allowing consumers to react to transient and persistent user activation without imposing side effects or control flow.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```tsx
|
|
26
|
+
* const { isActive, hasBeenActive } = useUserActivation();
|
|
27
|
+
*
|
|
28
|
+
* if (!hasBeenActive) {
|
|
29
|
+
* return <p>Please interact with the page.</p>;
|
|
30
|
+
* }
|
|
31
|
+
*
|
|
32
|
+
* if (isActive) {
|
|
33
|
+
* runSensitiveAction();
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/UserActivation
|
|
37
|
+
*/
|
|
38
|
+
declare function useUserActivation(): UseUserActivationReturn;
|
|
39
|
+
//#endregion
|
|
40
|
+
export { useUserActivation };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/useUserActivation.ts
|
|
4
|
+
/**
|
|
5
|
+
* `useUserActivation` is a React hook that exposes the browser User Activation State.
|
|
6
|
+
* This hook provides read-only access to `navigator.userActivation` allowing consumers to react to transient and persistent user activation without imposing side effects or control flow.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const { isActive, hasBeenActive } = useUserActivation();
|
|
11
|
+
*
|
|
12
|
+
* if (!hasBeenActive) {
|
|
13
|
+
* return <p>Please interact with the page.</p>;
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* if (isActive) {
|
|
17
|
+
* runSensitiveAction();
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/UserActivation
|
|
21
|
+
*/
|
|
22
|
+
function useUserActivation() {
|
|
23
|
+
const isSupported = typeof navigator !== "undefined" && typeof navigator.userActivation !== "undefined";
|
|
24
|
+
const readState = React.useCallback(() => {
|
|
25
|
+
if (!isSupported) return {
|
|
26
|
+
hasBeenActive: false,
|
|
27
|
+
isActive: false
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
hasBeenActive: navigator.userActivation.hasBeenActive,
|
|
31
|
+
isActive: navigator.userActivation.isActive
|
|
32
|
+
};
|
|
33
|
+
}, [isSupported]);
|
|
34
|
+
const [state, setState] = React.useState(() => readState());
|
|
35
|
+
const refresh = React.useCallback(() => {
|
|
36
|
+
setState(readState());
|
|
37
|
+
}, [readState]);
|
|
38
|
+
React.useEffect(() => {
|
|
39
|
+
if (!isSupported) return;
|
|
40
|
+
const events = [
|
|
41
|
+
"mousedown",
|
|
42
|
+
"keydown",
|
|
43
|
+
"touchstart",
|
|
44
|
+
"pointerdown"
|
|
45
|
+
];
|
|
46
|
+
const handler = () => {
|
|
47
|
+
refresh();
|
|
48
|
+
};
|
|
49
|
+
events.forEach((event) => window.addEventListener(event, handler, { passive: true }));
|
|
50
|
+
return () => {
|
|
51
|
+
events.forEach((event) => window.removeEventListener(event, handler));
|
|
52
|
+
};
|
|
53
|
+
}, [isSupported, refresh]);
|
|
54
|
+
return {
|
|
55
|
+
hasBeenActive: state.hasBeenActive,
|
|
56
|
+
isActive: state.isActive,
|
|
57
|
+
isSupported,
|
|
58
|
+
refresh
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
export { useUserActivation };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
//#region src/useVibration.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Pattern accepted by the Vibration API.
|
|
4
|
+
* A single duration in milliseconds or a vibration pattern array.
|
|
5
|
+
*/
|
|
6
|
+
type VibrationPattern = number | number[];
|
|
7
|
+
interface UseVibrationReturn {
|
|
8
|
+
/**
|
|
9
|
+
* Cancels any ongoing vibration.
|
|
10
|
+
* During SSR or when unsupported, does nothing.
|
|
11
|
+
*/
|
|
12
|
+
cancel: () => void;
|
|
13
|
+
/**
|
|
14
|
+
* Indicates whether the Vibration API is supported in the current execution environment.
|
|
15
|
+
*/
|
|
16
|
+
isSupported: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Triggers a vibration with the given pattern.
|
|
19
|
+
* Returns `true` if the vibration request was accepted by the user agent, or `false` otherwise.
|
|
20
|
+
* Durring SSR or when unsupported, returns `false`.
|
|
21
|
+
*/
|
|
22
|
+
vibrate: (pattern: VibrationPattern) => boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* `useVibration` is a React hook to safely access the Vibration API in an unopinionated and SSR-safe manner.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* function Example() {
|
|
30
|
+
* const { isSupported, vibrate, cancel } = useVibration();
|
|
31
|
+
*
|
|
32
|
+
* if (!isSupported) {
|
|
33
|
+
* return <button disabled>Vibration not supported</button>;
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* return (
|
|
37
|
+
* <>
|
|
38
|
+
* <button onClick={() => vibrate(200)}>
|
|
39
|
+
* Vibrate once
|
|
40
|
+
* </button>
|
|
41
|
+
* <button onClick={() => vibrate([100, 50, 100])}>
|
|
42
|
+
* Vibrate pattern
|
|
43
|
+
* </button>
|
|
44
|
+
* <button onClick={cancel}>
|
|
45
|
+
* Stop vibration
|
|
46
|
+
* </button>
|
|
47
|
+
* </>
|
|
48
|
+
* );
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Vibration_API
|
|
52
|
+
*/
|
|
53
|
+
declare function useVibration(): UseVibrationReturn;
|
|
54
|
+
//#endregion
|
|
55
|
+
export { useVibration };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/useVibration.ts
|
|
4
|
+
/**
|
|
5
|
+
* `useVibration` is a React hook to safely access the Vibration API in an unopinionated and SSR-safe manner.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* function Example() {
|
|
10
|
+
* const { isSupported, vibrate, cancel } = useVibration();
|
|
11
|
+
*
|
|
12
|
+
* if (!isSupported) {
|
|
13
|
+
* return <button disabled>Vibration not supported</button>;
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* return (
|
|
17
|
+
* <>
|
|
18
|
+
* <button onClick={() => vibrate(200)}>
|
|
19
|
+
* Vibrate once
|
|
20
|
+
* </button>
|
|
21
|
+
* <button onClick={() => vibrate([100, 50, 100])}>
|
|
22
|
+
* Vibrate pattern
|
|
23
|
+
* </button>
|
|
24
|
+
* <button onClick={cancel}>
|
|
25
|
+
* Stop vibration
|
|
26
|
+
* </button>
|
|
27
|
+
* </>
|
|
28
|
+
* );
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Vibration_API
|
|
32
|
+
*/
|
|
33
|
+
function useVibration() {
|
|
34
|
+
const isSupported = typeof window !== "undefined" && typeof navigator !== "undefined" && typeof navigator.vibrate === "function";
|
|
35
|
+
const vibrate = React.useCallback((pattern) => {
|
|
36
|
+
if (!isSupported) return false;
|
|
37
|
+
return navigator.vibrate(pattern);
|
|
38
|
+
}, [isSupported]);
|
|
39
|
+
return {
|
|
40
|
+
cancel: React.useCallback(() => {
|
|
41
|
+
if (!isSupported) return;
|
|
42
|
+
navigator.vibrate(0);
|
|
43
|
+
}, [isSupported]),
|
|
44
|
+
isSupported,
|
|
45
|
+
vibrate
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
export { useVibration };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
//#region src/useWebsocket.d.ts
|
|
2
|
+
interface UseWebsocketOptions<TMessage = unknown> {
|
|
3
|
+
/**
|
|
4
|
+
* Should the socket auto-connect inmediately (default: true)
|
|
5
|
+
*/
|
|
6
|
+
autoConnect?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Max number of reconnection attempts (default: Infinity)
|
|
9
|
+
*/
|
|
10
|
+
maxRetries?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Called when the socket closes unexpectedly
|
|
13
|
+
*/
|
|
14
|
+
onClose?: (event: CloseEvent) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Called when an error occurs
|
|
17
|
+
*/
|
|
18
|
+
onError?: (event: Event) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Called when a message is received
|
|
21
|
+
*/
|
|
22
|
+
onMessage?: (message: TMessage) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Reconnection interval in milliseconds (default: 3000)
|
|
25
|
+
*/
|
|
26
|
+
reconnectionInterval?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Websocket url
|
|
29
|
+
*/
|
|
30
|
+
url: string;
|
|
31
|
+
}
|
|
32
|
+
interface UseWebsocketReturn<TMessage = unknown> {
|
|
33
|
+
/**
|
|
34
|
+
* Manually reconnect the Websocket
|
|
35
|
+
*/
|
|
36
|
+
connect: () => void;
|
|
37
|
+
/**
|
|
38
|
+
* Manually disconnect the Websocket
|
|
39
|
+
*/
|
|
40
|
+
disconnect: () => void;
|
|
41
|
+
/**
|
|
42
|
+
* Show any errors
|
|
43
|
+
*/
|
|
44
|
+
error: Error | null;
|
|
45
|
+
/**
|
|
46
|
+
* Connection status
|
|
47
|
+
*/
|
|
48
|
+
isConnected: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Latest received message
|
|
51
|
+
*/
|
|
52
|
+
message: TMessage | null;
|
|
53
|
+
/**
|
|
54
|
+
* Messages
|
|
55
|
+
*/
|
|
56
|
+
messages: TMessage[];
|
|
57
|
+
/**
|
|
58
|
+
* Send a message via Websocket
|
|
59
|
+
*/
|
|
60
|
+
send: (data: string | ArrayBuffer | Blob | ArrayBufferView) => void;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* `useWebsocket` is a React hook to manage Websocket connections with auto-reconnect, error handling, and SSR safety.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```tsx
|
|
67
|
+
* const { socket, message, isConnected, send } = useWebsocket<{ text: string }>({
|
|
68
|
+
* url: 'wss://example.com/socket',
|
|
69
|
+
* onMessage: (msg) => console.log(msg),
|
|
70
|
+
* });
|
|
71
|
+
*
|
|
72
|
+
* React.useEffect(() => {
|
|
73
|
+
* if (isConnected) send(JSON.stringify({ hello: 'world' }));
|
|
74
|
+
* }, [isConnected]);
|
|
75
|
+
* ```
|
|
76
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
|
|
77
|
+
*/
|
|
78
|
+
declare function useWebsocket<TMessage = unknown>(options: UseWebsocketOptions<TMessage>): UseWebsocketReturn<TMessage>;
|
|
79
|
+
//#endregion
|
|
80
|
+
export { useWebsocket };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/useWebsocket.ts
|
|
4
|
+
/**
|
|
5
|
+
* `useWebsocket` is a React hook to manage Websocket connections with auto-reconnect, error handling, and SSR safety.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* const { socket, message, isConnected, send } = useWebsocket<{ text: string }>({
|
|
10
|
+
* url: 'wss://example.com/socket',
|
|
11
|
+
* onMessage: (msg) => console.log(msg),
|
|
12
|
+
* });
|
|
13
|
+
*
|
|
14
|
+
* React.useEffect(() => {
|
|
15
|
+
* if (isConnected) send(JSON.stringify({ hello: 'world' }));
|
|
16
|
+
* }, [isConnected]);
|
|
17
|
+
* ```
|
|
18
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
|
|
19
|
+
*/
|
|
20
|
+
function useWebsocket(options) {
|
|
21
|
+
const { autoConnect = true, maxRetries = Infinity, onClose, onError, onMessage, reconnectionInterval = 3e3, url } = options;
|
|
22
|
+
const [message, setMessage] = React.useState(null);
|
|
23
|
+
const [messages, setMessages] = React.useState([]);
|
|
24
|
+
const [isConnected, setIsConnected] = React.useState(false);
|
|
25
|
+
const [error, setError] = React.useState(null);
|
|
26
|
+
const socketRef = React.useRef(null);
|
|
27
|
+
const retriesRef = React.useRef(0);
|
|
28
|
+
const reconnectTimeoutRef = React.useRef(null);
|
|
29
|
+
const manualCloseRef = React.useRef(false);
|
|
30
|
+
const hasOpenedRef = React.useRef(false);
|
|
31
|
+
const onMessageRef = React.useRef(onMessage);
|
|
32
|
+
const onCloseRef = React.useRef(onClose);
|
|
33
|
+
const onErrorRef = React.useRef(onError);
|
|
34
|
+
React.useEffect(() => {
|
|
35
|
+
onMessageRef.current = onMessage;
|
|
36
|
+
onCloseRef.current = onClose;
|
|
37
|
+
onErrorRef.current = onError;
|
|
38
|
+
}, [
|
|
39
|
+
onMessage,
|
|
40
|
+
onClose,
|
|
41
|
+
onError
|
|
42
|
+
]);
|
|
43
|
+
const connect = React.useCallback(() => {
|
|
44
|
+
if (typeof window === "undefined") return;
|
|
45
|
+
manualCloseRef.current = false;
|
|
46
|
+
hasOpenedRef.current = false;
|
|
47
|
+
const ws = new WebSocket(url);
|
|
48
|
+
socketRef.current = ws;
|
|
49
|
+
ws.onopen = () => {
|
|
50
|
+
hasOpenedRef.current = true;
|
|
51
|
+
setIsConnected(true);
|
|
52
|
+
setError(null);
|
|
53
|
+
retriesRef.current = 0;
|
|
54
|
+
};
|
|
55
|
+
ws.onmessage = (event) => {
|
|
56
|
+
let data;
|
|
57
|
+
try {
|
|
58
|
+
data = JSON.parse(event.data);
|
|
59
|
+
} catch {
|
|
60
|
+
data = event.data;
|
|
61
|
+
}
|
|
62
|
+
setMessage(data);
|
|
63
|
+
setMessages((prev) => [...prev, data]);
|
|
64
|
+
onMessageRef.current?.(data);
|
|
65
|
+
};
|
|
66
|
+
ws.onclose = (event) => {
|
|
67
|
+
setIsConnected(false);
|
|
68
|
+
onCloseRef.current?.(event);
|
|
69
|
+
if (hasOpenedRef.current && !manualCloseRef.current && retriesRef.current < maxRetries) {
|
|
70
|
+
retriesRef.current += 1;
|
|
71
|
+
reconnectTimeoutRef.current = setTimeout(connect, reconnectionInterval);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
ws.onerror = (event) => {
|
|
75
|
+
setError((prev) => prev ?? /* @__PURE__ */ new Error("WebSocket error"));
|
|
76
|
+
onErrorRef.current?.(event);
|
|
77
|
+
};
|
|
78
|
+
}, [
|
|
79
|
+
url,
|
|
80
|
+
reconnectionInterval,
|
|
81
|
+
maxRetries
|
|
82
|
+
]);
|
|
83
|
+
const disconnect = React.useCallback(() => {
|
|
84
|
+
manualCloseRef.current = true;
|
|
85
|
+
if (socketRef.current) {
|
|
86
|
+
socketRef.current.close();
|
|
87
|
+
socketRef.current = null;
|
|
88
|
+
}
|
|
89
|
+
if (reconnectTimeoutRef.current) clearTimeout(reconnectTimeoutRef.current);
|
|
90
|
+
}, []);
|
|
91
|
+
const reconnect = React.useCallback(() => {
|
|
92
|
+
disconnect();
|
|
93
|
+
connect();
|
|
94
|
+
}, [disconnect, connect]);
|
|
95
|
+
React.useEffect(() => {
|
|
96
|
+
if (!autoConnect) return;
|
|
97
|
+
connect();
|
|
98
|
+
return () => {
|
|
99
|
+
disconnect();
|
|
100
|
+
};
|
|
101
|
+
}, [
|
|
102
|
+
autoConnect,
|
|
103
|
+
connect,
|
|
104
|
+
disconnect
|
|
105
|
+
]);
|
|
106
|
+
return {
|
|
107
|
+
connect: reconnect,
|
|
108
|
+
disconnect,
|
|
109
|
+
error,
|
|
110
|
+
isConnected,
|
|
111
|
+
message,
|
|
112
|
+
messages,
|
|
113
|
+
send: React.useCallback((data) => {
|
|
114
|
+
const socket = socketRef.current;
|
|
115
|
+
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
116
|
+
setError(/* @__PURE__ */ new Error("WebSocket is not connected. Message not sent."));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
socket.send(data);
|
|
120
|
+
}, [])
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
//#endregion
|
|
125
|
+
export { useWebsocket };
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vibehooks/react",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"description": "Modern React and Next.js hooks, unopinionated and focused on developer experience.",
|
|
6
|
+
"author": "Sebastian Marat Urdanegui Bisalaya <sebasurdanegui@gmail.com>",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://vibehooks.sebastianurdanegui.com",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/SebastianUrdaneguiBisalaya/vibehooks.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/SebastianUrdaneguiBisalaya/vibehooks/issues"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./dist/index.js",
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"main": "./dist/index.js",
|
|
21
|
+
"module": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"sideEffects": false,
|
|
30
|
+
"lint-staged": {
|
|
31
|
+
"*.{js,ts,tsx,mdx}": [
|
|
32
|
+
"eslint --fix",
|
|
33
|
+
"prettier --write"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"react": "^19.2.0",
|
|
38
|
+
"react-dom": "^19.2.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@commitlint/cli": "^20.4.1",
|
|
42
|
+
"@commitlint/config-conventional": "^20.4.1",
|
|
43
|
+
"@eslint/js": "^9.39.2",
|
|
44
|
+
"@tsconfig/strictest": "^2.0.8",
|
|
45
|
+
"@types/node": "^25.0.3",
|
|
46
|
+
"@types/react": "^19.2.7",
|
|
47
|
+
"@types/react-dom": "^19.2.3",
|
|
48
|
+
"bumpp": "^10.3.2",
|
|
49
|
+
"eslint": "^9.39.2",
|
|
50
|
+
"eslint-plugin-perfectionist": "^5.4.0",
|
|
51
|
+
"eslint-plugin-react": "^7.37.5",
|
|
52
|
+
"globals": "^17.0.0",
|
|
53
|
+
"husky": "^9.1.7",
|
|
54
|
+
"lint-staged": "^16.2.7",
|
|
55
|
+
"prettier": "3.7.4",
|
|
56
|
+
"tsdown": "^0.18.1",
|
|
57
|
+
"typescript": "^5.9.3",
|
|
58
|
+
"typescript-eslint": "^8.53.1"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "tsdown",
|
|
62
|
+
"dev": "tsdown --watch",
|
|
63
|
+
"typecheck": "tsc --noEmit",
|
|
64
|
+
"release": "bumpp && pnpm publish",
|
|
65
|
+
"prettier-check": "prettier --check .",
|
|
66
|
+
"prettier-fix": "prettier --write .",
|
|
67
|
+
"lint": "eslint .",
|
|
68
|
+
"lint-fix": "eslint . --fix"
|
|
69
|
+
}
|
|
70
|
+
}
|