From 1c8476df534c6a9f2d2395895c56ec5523992409 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Mon, 6 Jan 2025 11:43:36 -0500 Subject: [PATCH] refactor: consolidate browser feature detection into typed hook. Update connect dialog messaging to describe requirement for https when conneecting --- src/components/Dialog/NewDeviceDialog.tsx | 73 +++++++++++++------- src/core/hooks/useBrowserFeatureDetection.ts | 32 +++++++++ 2 files changed, 80 insertions(+), 25 deletions(-) create mode 100644 src/core/hooks/useBrowserFeatureDetection.ts diff --git a/src/components/Dialog/NewDeviceDialog.tsx b/src/components/Dialog/NewDeviceDialog.tsx index 6062d69c..92d4c75a 100644 --- a/src/components/Dialog/NewDeviceDialog.tsx +++ b/src/components/Dialog/NewDeviceDialog.tsx @@ -1,3 +1,4 @@ +import { useBrowserFeatureDetection } from "@app/core/hooks/useBrowserFeatureDetection"; import { BLE } from "@components/PageComponents/Connect/BLE.tsx"; import { HTTP } from "@components/PageComponents/Connect/HTTP.tsx"; import { Serial } from "@components/PageComponents/Connect/Serial.tsx"; @@ -28,30 +29,8 @@ export interface TabManifest { disabledLink?: string; } -const tabs: TabManifest[] = [ - { - label: "HTTP", - element: HTTP, - disabled: false, - disabledMessage: "Unsuported connection method", - }, - { - label: "Bluetooth", - element: BLE, - disabled: !navigator.bluetooth, - disabledMessage: - "Web Bluetooth is currently only supported by Chromium-based browsers", - disabledLink: - "https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility", - }, - { - label: "Serial", - element: Serial, - disabled: !navigator.serial, - disabledMessage: - "WebSerial is currently only supported by Chromium based browsers: https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API#browser_compatibility", - }, -]; + + export interface NewDeviceProps { open: boolean; onOpenChange: (open: boolean) => void; @@ -61,6 +40,36 @@ export const NewDeviceDialog = ({ open, onOpenChange, }: NewDeviceProps): JSX.Element => { + const { hasRequiredFeatures, isSecureContext, missingFeatures } = useBrowserFeatureDetection(); + console.log(missingFeatures); + + const tabs: TabManifest[] = [ + { + label: "HTTP", + element: HTTP, + disabled: false, + disabledMessage: "Unsuported connection method", + }, + { + label: "Bluetooth", + element: BLE, + disabled: missingFeatures.includes("Web Bluetooth"), + disabledMessage: + "Web Bluetooth is currently only supported by Chromium-based browsers", + disabledLink: + "https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API#browser_compatibility" + }, + { + label: "Serial", + element: Serial, + disabled: missingFeatures.includes("Web Serial"), + disabledMessage: + "Web Serial is currently only supported by Chromium based browsers", + disabledLink: "https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility", + }, + ]; + + return ( @@ -92,7 +101,21 @@ export const NewDeviceDialog = ({ ))} - {(!navigator.bluetooth || !navigator.serial) && ( + {!isSecureContext && ( + <> + + Web Bluetooth and Web Serial require using a HTTPS connection or to localhost. + + + Read more:  + + Secure Contexts + + + + )} + + {!hasRequiredFeatures && ( <> Web Bluetooth and Web Serial are currently only supported by diff --git a/src/core/hooks/useBrowserFeatureDetection.ts b/src/core/hooks/useBrowserFeatureDetection.ts new file mode 100644 index 00000000..df103b2c --- /dev/null +++ b/src/core/hooks/useBrowserFeatureDetection.ts @@ -0,0 +1,32 @@ +type Feature = 'Web Bluetooth' | 'Web Serial'; +type FeatureKey = 'bluetooth' | 'serial'; + +interface BrowserFeatureDetection { + hasRequiredFeatures: boolean; + missingFeatures: Feature[]; + isSecureContext: boolean; +} + +const featureLabels: Record = { + bluetooth: 'Web Bluetooth', + serial: 'Web Serial' +}; + +export function useBrowserFeatureDetection(): BrowserFeatureDetection { + const { bluetooth, serial } = navigator; + const isSecureContext = window.location.protocol === 'https:' || + window.location.hostname === 'localhost'; + + const features = { + bluetooth, + serial + }; + + return { + hasRequiredFeatures: Object.values(features).every(Boolean), + missingFeatures: Object.entries(features) + .filter(([_, supported]) => !supported) + .map(([feature]) => featureLabels[feature as FeatureKey]), + isSecureContext + }; +} \ No newline at end of file