import { observable, ObservableHint } from "@legendapp/state"; import { createSupported, onMount } from "@usels/core"; import type { DeepMaybeObservable, ReadonlyObservable, Supportable } from "@usels/core"; import { type ConfigurableWindow, defaultNavigator, defaultWindow } from "@shared/configurable"; import type { MaybeEventTarget } from "../../types"; import { createEventListener } from "../../browser/useEventListener/core"; import { createWhenMounted } from "@usels/core/utilities/useWhenMounted/core"; export type NetworkType = | "bluetooth" | "cellular" | "ethernet" | "none" | "wifi" | "wimax" | "other" | "unknown"; export type NetworkEffectiveType = "slow-2g" | "2g" | "3g" | "4g"; // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface UseNetworkOptions extends ConfigurableWindow {} export interface UseNetworkReturn extends Supportable { /** Whether the browser is online */ isOnline$: ReadonlyObservable; /** Timestamp of last offline transition */ offlineAt$: ReadonlyObservable; /** Timestamp of last online transition */ onlineAt$: ReadonlyObservable; /** Downlink speed in Mbps */ downlink$: ReadonlyObservable; /** Max reachable downlink speed in Mbps */ downlinkMax$: ReadonlyObservable; /** Effective connection type */ effectiveType$: ReadonlyObservable; /** Estimated round-trip time in ms */ rtt$: ReadonlyObservable; /** Whether data-saver mode is active */ saveData$: ReadonlyObservable; /** Connection type */ type$: ReadonlyObservable; } interface NetworkInformation extends EventTarget { readonly downlink?: number; readonly downlinkMax?: number; readonly effectiveType?: NetworkEffectiveType; readonly rtt?: number; readonly saveData?: boolean; readonly type?: NetworkType; } /*@__NO_SIDE_EFFECTS__*/ export function createNetwork(_options?: DeepMaybeObservable): UseNetworkReturn { const isSupported$ = createSupported( () => !!defaultNavigator && "connection" in defaultNavigator ); const isOnline$ = observable(true); const offlineAt$ = observable(undefined); const onlineAt$ = observable(undefined); const downlink$ = observable(undefined); const downlinkMax$ = observable(undefined); const effectiveType$ = observable(undefined); const rtt$ = observable(undefined); const saveData$ = observable(undefined); const type$ = observable("unknown"); const updateConnectionInfo = () => { if (!defaultNavigator || !("connection" in defaultNavigator)) return; const conn = (defaultNavigator as { connection?: NetworkInformation }).connection; if (!conn) return; downlink$.set(conn.downlink); downlinkMax$.set(conn.downlinkMax); effectiveType$.set(conn.effectiveType); rtt$.set(conn.rtt); saveData$.set(conn.saveData); type$.set(conn.type ?? "unknown"); }; const _conn$ = createWhenMounted(() => { const con = isSupported$.get() && defaultNavigator ? (defaultNavigator as { connection?: NetworkInformation }).connection : null; return con ? ObservableHint.opaque(con) : null; }); onMount(() => { if (!defaultWindow) return; isOnline$.set(defaultNavigator?.onLine ?? true); updateConnectionInfo(); }); createEventListener(_conn$, "change", updateConnectionInfo); createEventListener( (defaultWindow as MaybeEventTarget) ?? null, "online", () => { isOnline$.set(true); onlineAt$.set(Date.now()); offlineAt$.set(undefined); updateConnectionInfo(); }, { passive: true } ); createEventListener( (defaultWindow as MaybeEventTarget) ?? null, "offline", () => { isOnline$.set(false); offlineAt$.set(Date.now()); onlineAt$.set(undefined); updateConnectionInfo(); }, { passive: true } ); return { isSupported$, isOnline$, offlineAt$, onlineAt$, downlink$, downlinkMax$, effectiveType$, rtt$, saveData$, type$, }; }