From 5d38a6cdcd12ed1a127cce48c7422542e94ff4e0 Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Mon, 20 Jan 2025 10:08:45 +0100 Subject: [PATCH 1/7] Add packageManager --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 05221d9b..645a49c2 100644 --- a/package.json +++ b/package.json @@ -83,5 +83,6 @@ "tar": "^6.2.1", "tslib": "^2.6.3", "typescript": "^5.5.2" - } + }, + "packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0" } From 11f38f508964f3f4687e19e5f10dfb9a1f29d867 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Mon, 6 Jan 2025 11:43:36 -0500 Subject: [PATCH 2/7] 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 From 69b689a763832b43490ec95cea38011891a40a5f Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Wed, 15 Jan 2025 20:11:46 -0500 Subject: [PATCH 3/7] Improved error messaging based on feature missing from browser --- src/components/Dialog/NewDeviceDialog.tsx | 138 ++++++++++++------- src/components/UI/Typography/Link.tsx | 7 +- src/core/hooks/useBrowserFeatureDetection.ts | 49 ++++--- 3 files changed, 117 insertions(+), 77 deletions(-) diff --git a/src/components/Dialog/NewDeviceDialog.tsx b/src/components/Dialog/NewDeviceDialog.tsx index 92d4c75a..e03668d7 100644 --- a/src/components/Dialog/NewDeviceDialog.tsx +++ b/src/components/Dialog/NewDeviceDialog.tsx @@ -1,4 +1,7 @@ -import { useBrowserFeatureDetection } from "@app/core/hooks/useBrowserFeatureDetection"; +import { + type BrowserFeature, + 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"; @@ -8,14 +11,16 @@ import { DialogHeader, DialogTitle, } from "@components/UI/Dialog.tsx"; +import { AlertCircle, } from "lucide-react"; import { Tabs, TabsContent, TabsList, TabsTrigger, } from "@components/UI/Tabs.tsx"; -import { Link } from "@components/UI/Typography/Link.tsx"; import { Subtle } from "@components/UI/Typography/Subtle.tsx"; +import { Link } from "../UI/Typography/Link"; +import { Fragment } from "react/jsx-runtime"; export interface TabElementProps { closeDialog: () => void; @@ -29,19 +34,88 @@ export interface TabManifest { disabledLink?: string; } - - export interface NewDeviceProps { open: boolean; onOpenChange: (open: boolean) => void; } +interface FeatureErrorProps { + missingFeatures: BrowserFeature[]; +} + +const links: { [key: string]: string } = { + "Web Bluetooth": + "https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API#browser_compatibility", + "Web Serial": + "https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility", + "Secure Context": + "https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts", +}; + +const listFormatter = new Intl.ListFormat('en', { + style: 'long', + type: 'conjunction' +}); + +const ErrorMessage = ({ missingFeatures }: FeatureErrorProps) => { + if (missingFeatures.length === 0) return null; + + const browserFeatures = missingFeatures.filter(feature => feature !== "Secure Context"); + const needsSecureContext = missingFeatures.includes("Secure Context"); + + const formatFeatureList = (features: string[]) => { + const parts = listFormatter.formatToParts(features); + return parts.map((part) => { + if (part.type === 'element') { + return ( + + {part.value} + + ); + } + return {part.value}; + }); + }; + + return ( + +
+ +
+

+ {browserFeatures.length > 0 && ( + <> + This application requires {formatFeatureList(browserFeatures)}. + Please use a Chromium-based browser like Chrome or Edge. + + )} + {needsSecureContext && ( + <> + {browserFeatures.length > 0 && " Additionally, it"} + {browserFeatures.length === 0 && "This application"} requires a{" "} + + secure context + + . Please connect using HTTPS or localhost. + + )} +

+
+
+
+ ); +}; + export const NewDeviceDialog = ({ open, onOpenChange, }: NewDeviceProps): JSX.Element => { - const { hasRequiredFeatures, isSecureContext, missingFeatures } = useBrowserFeatureDetection(); - console.log(missingFeatures); + const { unsupported } = useBrowserFeatureDetection(); const tabs: TabManifest[] = [ { @@ -53,23 +127,21 @@ export const NewDeviceDialog = ({ { 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" + disabled: + unsupported.includes("Web Bluetooth") || + unsupported.includes("Secure Context"), + disabledMessage: "", }, { 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", + disabled: + unsupported.includes("Web Serial") || + unsupported.includes("Secure Context"), + disabledMessage: "", }, ]; - return ( @@ -99,40 +171,8 @@ export const NewDeviceDialog = ({ )} ))} + - - {!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 - Chromium-based browsers. - - - Read more:  - - Web Bluetooth - -   - - Web Serial - - - - )} ); diff --git a/src/components/UI/Typography/Link.tsx b/src/components/UI/Typography/Link.tsx index af1a90ac..efcfbdf6 100644 --- a/src/components/UI/Typography/Link.tsx +++ b/src/components/UI/Typography/Link.tsx @@ -1,14 +1,17 @@ +import { cn } from "@app/core/utils/cn"; + export interface LinkProps { href: string; children: React.ReactNode; + className?: string; } -export const Link = ({ href, children }: LinkProps): JSX.Element => ( +export const Link = ({ href, children, className }: LinkProps): JSX.Element => ( {children} diff --git a/src/core/hooks/useBrowserFeatureDetection.ts b/src/core/hooks/useBrowserFeatureDetection.ts index df103b2c..83a90a7a 100644 --- a/src/core/hooks/useBrowserFeatureDetection.ts +++ b/src/core/hooks/useBrowserFeatureDetection.ts @@ -1,32 +1,29 @@ -type Feature = 'Web Bluetooth' | 'Web Serial'; -type FeatureKey = 'bluetooth' | 'serial'; +import { useMemo } from 'react'; -interface BrowserFeatureDetection { - hasRequiredFeatures: boolean; - missingFeatures: Feature[]; - isSecureContext: boolean; -} +export type BrowserFeature = 'Web Bluetooth' | 'Web Serial' | 'Secure Context'; -const featureLabels: Record = { - bluetooth: 'Web Bluetooth', - serial: 'Web Serial' -}; +interface BrowserSupport { + supported: BrowserFeature[]; + unsupported: BrowserFeature[]; +} -export function useBrowserFeatureDetection(): BrowserFeatureDetection { - const { bluetooth, serial } = navigator; - const isSecureContext = window.location.protocol === 'https:' || - window.location.hostname === 'localhost'; +export function useBrowserFeatureDetection(): BrowserSupport { + const support = useMemo(() => { + const features: [BrowserFeature, boolean][] = [ + ['Web Bluetooth', !!navigator.bluetooth], + ['Web Serial', !!navigator.serial], + ['Secure Context', window.location.protocol === 'https:' || window.location.hostname === 'localhost'] + ]; - const features = { - bluetooth, - serial - }; + return features.reduce( + (acc, [feature, isSupported]) => { + const list = isSupported ? acc.supported : acc.unsupported; + list.push(feature); + return acc; + }, + { supported: [], unsupported: [] } + ); + }, []); - return { - hasRequiredFeatures: Object.values(features).every(Boolean), - missingFeatures: Object.entries(features) - .filter(([_, supported]) => !supported) - .map(([feature]) => featureLabels[feature as FeatureKey]), - isSecureContext - }; + return support; } \ No newline at end of file From 5f30f5274c8e7a1fd35f66b777b06da9875f5384 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Sun, 19 Jan 2025 22:42:38 -0500 Subject: [PATCH 4/7] fix: changed position of error message, disabled buttons when error is showing --- src/components/Dialog/NewDeviceDialog.tsx | 26 ++++++------------- .../PageComponents/Connect/Serial.tsx | 11 ++++---- src/core/hooks/useBrowserFeatureDetection.ts | 4 +-- 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/components/Dialog/NewDeviceDialog.tsx b/src/components/Dialog/NewDeviceDialog.tsx index e03668d7..e3330cbf 100644 --- a/src/components/Dialog/NewDeviceDialog.tsx +++ b/src/components/Dialog/NewDeviceDialog.tsx @@ -11,7 +11,7 @@ import { DialogHeader, DialogTitle, } from "@components/UI/Dialog.tsx"; -import { AlertCircle, } from "lucide-react"; +import { AlertCircle, InfoIcon, } from "lucide-react"; import { Tabs, TabsContent, @@ -29,9 +29,7 @@ export interface TabElementProps { export interface TabManifest { label: string; element: React.FC; - disabled: boolean; - disabledMessage: string; - disabledLink?: string; + isDisabled: boolean; } export interface NewDeviceProps { @@ -121,24 +119,21 @@ export const NewDeviceDialog = ({ { label: "HTTP", element: HTTP, - disabled: false, - disabledMessage: "Unsuported connection method", + isDisabled: false, }, { label: "Bluetooth", element: BLE, - disabled: + isDisabled: unsupported.includes("Web Bluetooth") || unsupported.includes("Secure Context"), - disabledMessage: "", }, { label: "Serial", element: Serial, - disabled: + isDisabled: unsupported.includes("Web Serial") || unsupported.includes("Secure Context"), - disabledMessage: "", }, ]; @@ -154,7 +149,6 @@ export const NewDeviceDialog = ({ {tab.label} @@ -162,16 +156,12 @@ export const NewDeviceDialog = ({ {tabs.map((tab) => ( - {tab.disabled ? ( -

- {tab.disabledMessage} -

- ) : ( +
+ {tab.isDisabled ? : null} onOpenChange(false)} /> - )} +
))} -
diff --git a/src/components/PageComponents/Connect/Serial.tsx b/src/components/PageComponents/Connect/Serial.tsx index 38aab8fb..53c623cb 100644 --- a/src/components/PageComponents/Connect/Serial.tsx +++ b/src/components/PageComponents/Connect/Serial.tsx @@ -14,13 +14,13 @@ export const Serial = ({ closeDialog }: TabElementProps): JSX.Element => { const { setSelectedDevice } = useAppStore(); const updateSerialPortList = useCallback(async () => { - setSerialPorts(await navigator.serial.getPorts()); + setSerialPorts(await navigator?.serial.getPorts()); }, []); - navigator.serial.addEventListener("connect", () => { + navigator?.serial?.addEventListener("connect", () => { updateSerialPortList(); }); - navigator.serial.addEventListener("disconnect", () => { + navigator?.serial?.addEventListener("disconnect", () => { updateSerialPortList(); }); useEffect(() => { @@ -58,9 +58,8 @@ export const Serial = ({ closeDialog }: TabElementProps): JSX.Element => { await onConnect(port); }} > - {`# ${index} - ${usbVendorId ?? "UNK"} - ${ - usbProductId ?? "UNK" - }`} + {`# ${index} - ${usbVendorId ?? "UNK"} - ${usbProductId ?? "UNK" + }`} ); })} diff --git a/src/core/hooks/useBrowserFeatureDetection.ts b/src/core/hooks/useBrowserFeatureDetection.ts index 83a90a7a..a31b7b5a 100644 --- a/src/core/hooks/useBrowserFeatureDetection.ts +++ b/src/core/hooks/useBrowserFeatureDetection.ts @@ -10,8 +10,8 @@ interface BrowserSupport { export function useBrowserFeatureDetection(): BrowserSupport { const support = useMemo(() => { const features: [BrowserFeature, boolean][] = [ - ['Web Bluetooth', !!navigator.bluetooth], - ['Web Serial', !!navigator.serial], + ['Web Bluetooth', !!navigator?.bluetooth], + ['Web Serial', !!navigator?.serial], ['Secure Context', window.location.protocol === 'https:' || window.location.hostname === 'localhost'] ]; From f15d17e42d51ad674e28aba6f0d5113657a48f86 Mon Sep 17 00:00:00 2001 From: Hunter275 Date: Mon, 20 Jan 2025 20:20:50 -0500 Subject: [PATCH 5/7] remove version spec --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd73ce31..2591421c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,6 @@ jobs: - name: Checkout uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 - with: - version: latest - name: Install Dependencies run: pnpm install From 9794c661f191e8ff439993c6141995475124e4f8 Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Tue, 21 Jan 2025 20:56:49 +0100 Subject: [PATCH 6/7] Remove unused @buf/meshtastic_protobufs.bufbuild_es --- package.json | 3 +-- pnpm-lock.yaml | 12 ------------ 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/package.json b/package.json index 645a49c2..53911b41 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,6 @@ }, "devDependencies": { "@biomejs/biome": "^1.8.2", - "@buf/meshtastic_protobufs.bufbuild_es": "1.10.0-20240906232734-3da561588c55.1", "@rsbuild/core": "^1.0.10", "@rsbuild/plugin-react": "^1.0.3", "@types/chrome": "^0.0.263", @@ -84,5 +83,5 @@ "tslib": "^2.6.3", "typescript": "^5.5.2" }, - "packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0" + "packageManager": "pnpm@9.15.4" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9edbfd5..9060324c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,9 +135,6 @@ importers: '@biomejs/biome': specifier: ^1.8.2 version: 1.8.2 - '@buf/meshtastic_protobufs.bufbuild_es': - specifier: 1.10.0-20240906232734-3da561588c55.1 - version: 1.10.0-20240906232734-3da561588c55.1(@bufbuild/protobuf@1.10.0) '@rsbuild/core': specifier: ^1.0.10 version: 1.0.10 @@ -250,11 +247,6 @@ packages: cpu: [x64] os: [win32] - '@buf/meshtastic_protobufs.bufbuild_es@1.10.0-20240906232734-3da561588c55.1': - resolution: {tarball: https://buf.build/gen/npm/v1/@buf/meshtastic_protobufs.bufbuild_es/-/meshtastic_protobufs.bufbuild_es-1.10.0-20240906232734-3da561588c55.1.tgz} - peerDependencies: - '@bufbuild/protobuf': ^1.10.0 - '@bufbuild/protobuf@1.10.0': resolution: {integrity: sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==} @@ -3391,10 +3383,6 @@ snapshots: '@biomejs/cli-win32-x64@1.8.2': optional: true - '@buf/meshtastic_protobufs.bufbuild_es@1.10.0-20240906232734-3da561588c55.1(@bufbuild/protobuf@1.10.0)': - dependencies: - '@bufbuild/protobuf': 1.10.0 - '@bufbuild/protobuf@1.10.0': {} '@emeraldpay/hashicon-react@0.5.2': From 7fedcda4c969c792550e7ade43934dbbc4d2c557 Mon Sep 17 00:00:00 2001 From: Tilen Komel Date: Tue, 21 Jan 2025 21:13:43 +0100 Subject: [PATCH 7/7] Remove packageManager because ci doesnt likes it --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 53911b41..3bdcfe1f 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,5 @@ "tar": "^6.2.1", "tslib": "^2.6.3", "typescript": "^5.5.2" - }, - "packageManager": "pnpm@9.15.4" + } }