Skip to content

Commit e529bf5

Browse files
feat: add useNetworkStatus util
chore: add .idea to gitignore chore: clean up chore: clean up
1 parent 296c835 commit e529bf5

File tree

5 files changed

+295
-0
lines changed

5 files changed

+295
-0
lines changed

packages/runed/src/lib/utilities/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ export * from "./AnimationFrames/index.js";
2020
export * from "./useIntersectionObserver/index.js";
2121
export * from "./IsFocusWithin/index.js";
2222
export * from "./FiniteStateMachine/index.js";
23+
export * from "./useNetworkStatus/index.js";
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./useNetworkStatus.svelte.js";
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { addEventListener } from "$lib/internal/utils/event.js";
2+
import { Previous } from "$lib/utilities/index.js";
3+
import { browser } from "$app/environment";
4+
5+
/**
6+
* @desc The `NetworkInformation` interface of the Network Information API
7+
* @see https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation
8+
*/
9+
type NetworkInformation = {
10+
readonly downlink: number;
11+
readonly downlinkMax: number;
12+
readonly effectiveType: "slow-2g" | "2g" | "3g" | "4g";
13+
readonly rtt: number;
14+
readonly saveData: boolean;
15+
readonly type:
16+
| "bluetooth"
17+
| "cellular"
18+
| "ethernet"
19+
| "none"
20+
| "wifi"
21+
| "wimax"
22+
| "other"
23+
| "unknown";
24+
} & EventTarget;
25+
26+
type NavigatorWithConnection = Navigator & { connection: NetworkInformation };
27+
28+
interface NetworkStatus {
29+
/**
30+
* @desc Returns the online status of the browser.
31+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine
32+
*/
33+
online: boolean;
34+
/**
35+
* @desc The {Date} object pointing to the moment when state update occurred.
36+
*/
37+
updatedAt: Date;
38+
/**
39+
* @desc Effective bandwidth estimate in megabits per second, rounded to the
40+
* nearest multiple of 25 kilobits per seconds.
41+
* @see https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/downlink
42+
*/
43+
downlink?: NetworkInformation["downlink"];
44+
/**
45+
* @desc Maximum downlink speed, in megabits per second (Mbps), for the
46+
* underlying connection technology
47+
* @see https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/downlinkMax
48+
*/
49+
downlinkMax?: NetworkInformation["downlinkMax"];
50+
/**
51+
* @desc Effective type of the connection meaning one of 'slow-2g', '2g', '3g', or '4g'.
52+
* This value is determined using a combination of recently observed round-trip time
53+
* and downlink values.
54+
* @see https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/effectiveType
55+
*/
56+
effectiveType?: NetworkInformation["effectiveType"];
57+
/**
58+
* @desc Estimated effective round-trip time of the current connection, rounded
59+
* to the nearest multiple of 25 milliseconds
60+
* @see https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/rtt
61+
*/
62+
rtt?: NetworkInformation["rtt"];
63+
/**
64+
* @desc {true} if the user has set a reduced data usage option on the user agent.
65+
* @see https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/saveData
66+
*/
67+
saveData?: NetworkInformation["saveData"];
68+
/**
69+
* @desc The type of connection a device is using to communicate with the network.
70+
* It will be one of the following values:
71+
* - bluetooth
72+
* - cellular
73+
* - ethernet
74+
* - none
75+
* - wifi
76+
* - wimax
77+
* - other
78+
* - unknown
79+
* @see https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/type
80+
*/
81+
type?: NetworkInformation["type"];
82+
}
83+
84+
/**
85+
* Tracks the state of browser's network connection.
86+
* @returns null if the function is not run in the browser
87+
*/
88+
export function useNetworkStatus() {
89+
if (!browser) {
90+
return {
91+
get current() {
92+
return null;
93+
},
94+
get previous() {
95+
return null;
96+
},
97+
};
98+
}
99+
const navigator = window.navigator;
100+
const connection =
101+
"connection" in navigator ? (navigator as NavigatorWithConnection).connection : undefined;
102+
const getConnectionStatus = (): NetworkStatus => {
103+
return {
104+
online: navigator.onLine,
105+
updatedAt: new Date(),
106+
downlink: connection?.downlink,
107+
downlinkMax: connection?.downlinkMax,
108+
effectiveType: connection?.effectiveType,
109+
rtt: connection?.rtt,
110+
saveData: connection?.saveData,
111+
type: connection?.type,
112+
};
113+
};
114+
115+
let current = $state(getConnectionStatus());
116+
const previous = new Previous(() => current);
117+
const handleStatusChange = () => {
118+
current = getConnectionStatus();
119+
};
120+
121+
$effect(() => {
122+
// The connection event handler also manages online and offline states.
123+
if (connection) {
124+
addEventListener(connection, "change", handleStatusChange, { passive: true });
125+
} else {
126+
addEventListener(window, "online", handleStatusChange, { passive: true });
127+
addEventListener(window, "offline", handleStatusChange, { passive: true });
128+
}
129+
130+
return () => {
131+
window.removeEventListener("online", handleStatusChange);
132+
window.removeEventListener("online", handleStatusChange);
133+
connection?.removeEventListener("change", handleStatusChange);
134+
};
135+
});
136+
137+
return {
138+
get current() {
139+
return current;
140+
},
141+
get previous() {
142+
return previous.current;
143+
},
144+
};
145+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
---
2+
title: useNetworkStatus
3+
description: Watch for network status changes.
4+
category: Browser
5+
---
6+
7+
<script>
8+
import Demo from '$lib/components/demos/use-network-status.svelte';
9+
import { Callout } from '$lib/components'
10+
</script>
11+
12+
## Demo
13+
14+
To see it in action, try disabling and then re-enabling your Internet connection.
15+
If you're using Google Chrome, you can also [artificially throttle the network](https://developer.chrome.com/docs/devtools/settings/throttling) to test its behavior under different conditions.
16+
17+
<Demo />
18+
19+
## Usage
20+
21+
You can use `useNetworkStatus()` to retrieve both current and previous network status.
22+
It must be used within the `browser` context, otherwise it will return `null`.
23+
24+
```svelte
25+
<script lang="ts">
26+
import { useNetworkStatus } from "runed";
27+
import { toast } from "svelte-sonner";
28+
29+
const networkStatus = useNetworkStatus();
30+
31+
$effect(() => {
32+
if (!networkStatus.current) {
33+
return;
34+
}
35+
if (networkStatus.current.online === false) {
36+
toast.error("No internet connection.");
37+
}
38+
if (networkStatus.current.effectiveType === "2g") {
39+
toast.warning("You are experiencing a slow connection.");
40+
}
41+
if (networkStatus.current.online === true && networkStatus.previous?.online === false) {
42+
toast.success("You are back online!");
43+
}
44+
});
45+
</script>
46+
47+
48+
{#if networkStatus.current}
49+
<p>online: {networkStatus.current.online}</p>
50+
{/if}
51+
```
52+
53+
### Current
54+
55+
You can get the current status by calling the `current` method.
56+
57+
58+
```ts
59+
const networkStatus = useNetworkStatus();
60+
61+
networkStatus.current;
62+
```
63+
64+
### Previous
65+
66+
You can get the previous status by calling the `previous` method.
67+
It defaults to `undefined` if the network status hasn't been updated since the component mounted.
68+
69+
```ts
70+
const networkStatus = useNetworkStatus();
71+
72+
networkStatus.previous;
73+
```
74+
75+
## Reference
76+
77+
The returned status always includes `online` and `updatedAt`.
78+
Other properties are returned based on the [NetworkInformation](https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation#instance_properties) interface and depend on [your browser's compatibility](https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation#browser_compatibility).
79+
80+
```typescript
81+
interface NetworkStatus {
82+
online: boolean;
83+
updatedAt: Date;
84+
downlink?: number;
85+
downlinkMax?: number;
86+
effectiveType?: "slow-2g" | "2g" | "3g" | "4g";
87+
rtt?: number;
88+
saveData?: boolean;
89+
type?: "bluetooth" | "cellular" | "ethernet" | "none" | "wifi" | "wimax" | "other" | "unknown";
90+
}
91+
```
92+
93+
94+
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<script lang="ts">
2+
import { useNetworkStatus } from "runed";
3+
import { toast } from "svelte-sonner";
4+
import DemoContainer from "$lib/components/demo-container.svelte";
5+
6+
const networkStatus = useNetworkStatus();
7+
8+
const timeOffline = $derived(() => {
9+
if (networkStatus && networkStatus.previous) {
10+
const now = networkStatus.current.updatedAt;
11+
const prev = networkStatus.previous.updatedAt;
12+
if (!now || !prev) {
13+
return 0;
14+
}
15+
const differenceMs = now.getTime() - prev.getTime();
16+
if (differenceMs < 0) {
17+
return 0;
18+
}
19+
const differenceSeconds = differenceMs / 1000;
20+
return Math.round(differenceSeconds);
21+
}
22+
return 0;
23+
});
24+
25+
$effect(() => {
26+
if (!networkStatus.current) {
27+
return;
28+
}
29+
30+
if (networkStatus.current.online === false) {
31+
toast.error("No internet connection. Reconnect to continue using the app.");
32+
}
33+
if (
34+
networkStatus.current.effectiveType === "3g" ||
35+
networkStatus.current.effectiveType === "2g" ||
36+
networkStatus.current.effectiveType === "slow-2g"
37+
) {
38+
toast.warning(
39+
"You are experiencing a slow connection. Some features may take longer to load."
40+
);
41+
}
42+
if (networkStatus.current.online === true && networkStatus.previous?.online === false) {
43+
toast.success(
44+
`You are back online! Catch up with what you missed during your ${timeOffline()} seconds offline.`
45+
);
46+
}
47+
});
48+
</script>
49+
50+
<DemoContainer>
51+
{#if networkStatus.current}
52+
<pre>{JSON.stringify(networkStatus.current, null, 2)}</pre>
53+
{/if}
54+
</DemoContainer>

0 commit comments

Comments
 (0)