Skip to content

Commit

Permalink
feat: add useNetworkStatus util
Browse files Browse the repository at this point in the history
chore: add .idea to gitignore

chore: clean up

chore: clean up
  • Loading branch information
michal-weglarz committed Jul 11, 2024
1 parent 296c835 commit e529bf5
Show file tree
Hide file tree
Showing 5 changed files with 295 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/runed/src/lib/utilities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ export * from "./AnimationFrames/index.js";
export * from "./useIntersectionObserver/index.js";
export * from "./IsFocusWithin/index.js";
export * from "./FiniteStateMachine/index.js";
export * from "./useNetworkStatus/index.js";
1 change: 1 addition & 0 deletions packages/runed/src/lib/utilities/useNetworkStatus/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./useNetworkStatus.svelte.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { addEventListener } from "$lib/internal/utils/event.js";
import { Previous } from "$lib/utilities/index.js";
import { browser } from "$app/environment";

/**
* @desc The `NetworkInformation` interface of the Network Information API
* @see https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation
*/
type NetworkInformation = {
readonly downlink: number;
readonly downlinkMax: number;
readonly effectiveType: "slow-2g" | "2g" | "3g" | "4g";
readonly rtt: number;
readonly saveData: boolean;
readonly type:
| "bluetooth"
| "cellular"
| "ethernet"
| "none"
| "wifi"
| "wimax"
| "other"
| "unknown";
} & EventTarget;

type NavigatorWithConnection = Navigator & { connection: NetworkInformation };

interface NetworkStatus {
/**
* @desc Returns the online status of the browser.
* @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine
*/
online: boolean;
/**
* @desc The {Date} object pointing to the moment when state update occurred.
*/
updatedAt: Date;
/**
* @desc Effective bandwidth estimate in megabits per second, rounded to the
* nearest multiple of 25 kilobits per seconds.
* @see https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/downlink
*/
downlink?: NetworkInformation["downlink"];
/**
* @desc Maximum downlink speed, in megabits per second (Mbps), for the
* underlying connection technology
* @see https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/downlinkMax
*/
downlinkMax?: NetworkInformation["downlinkMax"];
/**
* @desc Effective type of the connection meaning one of 'slow-2g', '2g', '3g', or '4g'.
* This value is determined using a combination of recently observed round-trip time
* and downlink values.
* @see https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/effectiveType
*/
effectiveType?: NetworkInformation["effectiveType"];
/**
* @desc Estimated effective round-trip time of the current connection, rounded
* to the nearest multiple of 25 milliseconds
* @see https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/rtt
*/
rtt?: NetworkInformation["rtt"];
/**
* @desc {true} if the user has set a reduced data usage option on the user agent.
* @see https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/saveData
*/
saveData?: NetworkInformation["saveData"];
/**
* @desc The type of connection a device is using to communicate with the network.
* It will be one of the following values:
* - bluetooth
* - cellular
* - ethernet
* - none
* - wifi
* - wimax
* - other
* - unknown
* @see https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/type
*/
type?: NetworkInformation["type"];
}

/**
* Tracks the state of browser's network connection.
* @returns null if the function is not run in the browser
*/
export function useNetworkStatus() {
if (!browser) {
return {
get current() {
return null;
},
get previous() {
return null;
},
};
}
const navigator = window.navigator;
const connection =
"connection" in navigator ? (navigator as NavigatorWithConnection).connection : undefined;
const getConnectionStatus = (): NetworkStatus => {
return {
online: navigator.onLine,
updatedAt: new Date(),
downlink: connection?.downlink,
downlinkMax: connection?.downlinkMax,
effectiveType: connection?.effectiveType,
rtt: connection?.rtt,
saveData: connection?.saveData,
type: connection?.type,
};
};

let current = $state(getConnectionStatus());
const previous = new Previous(() => current);
const handleStatusChange = () => {
current = getConnectionStatus();
};

$effect(() => {
// The connection event handler also manages online and offline states.
if (connection) {
addEventListener(connection, "change", handleStatusChange, { passive: true });
} else {
addEventListener(window, "online", handleStatusChange, { passive: true });
addEventListener(window, "offline", handleStatusChange, { passive: true });
}

return () => {
window.removeEventListener("online", handleStatusChange);
window.removeEventListener("online", handleStatusChange);
connection?.removeEventListener("change", handleStatusChange);
};
});

return {
get current() {
return current;
},
get previous() {
return previous.current;
},
};
}
94 changes: 94 additions & 0 deletions sites/docs/content/utilities/use-network-status.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
title: useNetworkStatus
description: Watch for network status changes.
category: Browser
---

<script>
import Demo from '$lib/components/demos/use-network-status.svelte';
import { Callout } from '$lib/components'
</script>

## Demo

To see it in action, try disabling and then re-enabling your Internet connection.
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.

<Demo />

## Usage

You can use `useNetworkStatus()` to retrieve both current and previous network status.
It must be used within the `browser` context, otherwise it will return `null`.

```svelte
<script lang="ts">
import { useNetworkStatus } from "runed";
import { toast } from "svelte-sonner";
const networkStatus = useNetworkStatus();
$effect(() => {
if (!networkStatus.current) {
return;
}
if (networkStatus.current.online === false) {
toast.error("No internet connection.");
}
if (networkStatus.current.effectiveType === "2g") {
toast.warning("You are experiencing a slow connection.");
}
if (networkStatus.current.online === true && networkStatus.previous?.online === false) {
toast.success("You are back online!");
}
});
</script>
{#if networkStatus.current}
<p>online: {networkStatus.current.online}</p>
{/if}
```

### Current

You can get the current status by calling the `current` method.


```ts
const networkStatus = useNetworkStatus();

networkStatus.current;
```

### Previous

You can get the previous status by calling the `previous` method.
It defaults to `undefined` if the network status hasn't been updated since the component mounted.

```ts
const networkStatus = useNetworkStatus();

networkStatus.previous;
```

## Reference

The returned status always includes `online` and `updatedAt`.
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).

```typescript
interface NetworkStatus {
online: boolean;
updatedAt: Date;
downlink?: number;
downlinkMax?: number;
effectiveType?: "slow-2g" | "2g" | "3g" | "4g";
rtt?: number;
saveData?: boolean;
type?: "bluetooth" | "cellular" | "ethernet" | "none" | "wifi" | "wimax" | "other" | "unknown";
}
```



54 changes: 54 additions & 0 deletions sites/docs/src/lib/components/demos/use-network-status.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<script lang="ts">
import { useNetworkStatus } from "runed";
import { toast } from "svelte-sonner";
import DemoContainer from "$lib/components/demo-container.svelte";
const networkStatus = useNetworkStatus();
const timeOffline = $derived(() => {
if (networkStatus && networkStatus.previous) {
const now = networkStatus.current.updatedAt;
const prev = networkStatus.previous.updatedAt;
if (!now || !prev) {
return 0;
}
const differenceMs = now.getTime() - prev.getTime();
if (differenceMs < 0) {
return 0;
}
const differenceSeconds = differenceMs / 1000;
return Math.round(differenceSeconds);
}
return 0;
});
$effect(() => {
if (!networkStatus.current) {
return;
}
if (networkStatus.current.online === false) {
toast.error("No internet connection. Reconnect to continue using the app.");
}
if (
networkStatus.current.effectiveType === "3g" ||
networkStatus.current.effectiveType === "2g" ||
networkStatus.current.effectiveType === "slow-2g"
) {
toast.warning(
"You are experiencing a slow connection. Some features may take longer to load."
);
}
if (networkStatus.current.online === true && networkStatus.previous?.online === false) {
toast.success(
`You are back online! Catch up with what you missed during your ${timeOffline()} seconds offline.`
);
}
});
</script>

<DemoContainer>
{#if networkStatus.current}
<pre>{JSON.stringify(networkStatus.current, null, 2)}</pre>
{/if}
</DemoContainer>

0 comments on commit e529bf5

Please sign in to comment.