diff --git a/.changeset/funny-snails-double.md b/.changeset/funny-snails-double.md new file mode 100644 index 00000000..89ecb118 --- /dev/null +++ b/.changeset/funny-snails-double.md @@ -0,0 +1,5 @@ +--- +"@slashid/react": minor +--- + +Automatically hide passkey option if platform or browser does not support good passkey UX diff --git a/packages/react/package.json b/packages/react/package.json index 58946ada..e993d4d0 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -65,7 +65,7 @@ }, "devDependencies": { "@faker-js/faker": "^8.0.2", - "@slashid/slashid": "3.18.2", + "@slashid/slashid": "3.19.0", "@storybook/addon-essentials": "7.0.0-rc.2", "@storybook/addon-interactions": "7.4.0", "@storybook/addon-links": "7.4.0", @@ -100,7 +100,7 @@ "yalc": "1.0.0-pre.53" }, "peerDependencies": { - "@slashid/slashid": ">= 3.18.2", + "@slashid/slashid": ">=3.19.0", "react": ">=16", "react-dom": ">=16" } diff --git a/packages/react/src/components/form/initial/controls.tsx b/packages/react/src/components/form/initial/controls.tsx index 01bd9857..68bd60bf 100644 --- a/packages/react/src/components/form/initial/controls.tsx +++ b/packages/react/src/components/form/initial/controls.tsx @@ -24,6 +24,7 @@ import { useForm } from "../../../hooks/use-form"; import { TextConfig, TextConfigKey } from "../../text/constants"; import { ErrorMessage } from "../error-message"; import { isValidEmail, isValidPhoneNumber } from "../authenticating/validation"; +import { runtimeSupportsPasskeys } from "../../../passkey"; import * as styles from "./initial.css"; import { useInternalFormContext } from "../internal-context"; @@ -172,16 +173,33 @@ const FormInput = ({ children }: FormInputProps) => { const { lastHandle } = useInternalFormContext(); const { factors, text } = useConfiguration(); + const [showPasskeys, setShowPasskeys] = useState(null); + useEffect(() => { + (async () => { + const isSupported = await runtimeSupportsPasskeys; + setShowPasskeys(isSupported); + })(); + }, []); + // @ts-expect-error TODO fix inference const nonOidcFactors: FactorNonOIDC[] = useMemo( - () => factors.filter((f) => isFactorNonOidc(f)), - [factors] + () => + factors.filter((f) => { + if (f.method === "webauthn" && showPasskeys === false) return false; + return isFactorNonOidc(f); + }), + [factors, showPasskeys] ); const handleTypes = useMemo(() => { return getHandleTypes(factors); }, [factors]); + const passkeySupportDetectionLoading = showPasskeys === null; + if (passkeySupportDetectionLoading) { + return null; + } + if (typeof children === "function") { return <>{children({ factors: nonOidcFactors, handleTypes })}; } diff --git a/packages/react/src/main.ts b/packages/react/src/main.ts index b3bbaa7b..bcdbf122 100644 --- a/packages/react/src/main.ts +++ b/packages/react/src/main.ts @@ -1,3 +1,4 @@ +import "./passkey"; // start this loading ASAP import { DynamicFlow } from "./components/dynamic-flow"; import { Form } from "./components/form"; import { Slot } from "./components/slot"; diff --git a/packages/react/src/passkey.ts b/packages/react/src/passkey.ts new file mode 100644 index 00000000..ff9a3db4 --- /dev/null +++ b/packages/react/src/passkey.ts @@ -0,0 +1,3 @@ +import { Utils } from "@slashid/slashid"; + +export const runtimeSupportsPasskeys = Utils.runtimeSupportsBestPasskeyUX(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d8cad21..bcc6dea9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -370,8 +370,8 @@ importers: specifier: ^8.0.2 version: 8.3.1 '@slashid/slashid': - specifier: 3.18.2 - version: 3.18.2 + specifier: 3.19.0 + version: 3.19.0 '@storybook/addon-essentials': specifier: 7.0.0-rc.2 version: 7.0.0-rc.2(react-dom@18.2.0)(react@18.2.0) @@ -8659,6 +8659,24 @@ packages: regenerator-runtime: 0.13.11 url: 0.11.3 uuid: 8.3.2 + dev: false + + /@slashid/slashid@3.19.0: + resolution: {integrity: sha512-MG2vGh+q7vKxqJbs9gA4CrWVh2aXjw9mKk4A8k9gewts/L+AcNZHWTe73Y0QvQ1L7GJhgLB3+tHgGgCLC133Lg==} + dependencies: + '@changesets/cli': 2.26.2 + '@types/uuid': 8.3.4 + changeset: 0.2.6 + compare-versions: 6.1.0 + docdash: 1.2.0 + jwt-decode: 3.1.2 + qrcode: 1.5.3 + querystring-es3: 0.2.1 + regenerator-runtime: 0.13.11 + ua-parser-js: 1.0.37 + url: 0.11.3 + uuid: 8.3.2 + dev: true /@storybook/addon-actions@7.0.0-rc.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-e4VXOoeejRnz+yTtPGtenAvrMtwRWO4VRxbGBHDPyMo+FOlQPC6plOrQMZ+lSRaE20J3KfTo6wSLZgnHQUQtRw==} @@ -12280,6 +12298,10 @@ packages: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} dev: true + /compare-versions@6.1.0: + resolution: {integrity: sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg==} + dev: true + /compress.js@1.2.2: resolution: {integrity: sha512-eIsMmzyBtPVDdPtpmPXTRB6uiLjgTXygYl0KCVVp0I/U/++YzZtEkhVTuWH97dzTxKvzAcZMFwYUHGC09BSUqg==} dependencies: @@ -18347,6 +18369,7 @@ packages: /qrcode@1.5.3: resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==} engines: {node: '>=10.13.0'} + hasBin: true dependencies: dijkstrajs: 1.0.3 encode-utf8: 1.0.3 @@ -20420,6 +20443,10 @@ packages: engines: {node: '>=14.17'} hasBin: true + /ua-parser-js@1.0.37: + resolution: {integrity: sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==} + dev: true + /udc@1.0.1: resolution: {integrity: sha512-jv+D9de1flsum5QkFtBdjyppCQAdz9kTck/0xST5Vx48T9LL2BYnw0Iw77dSKDQ9KZ/PS3qPO1vfXHDpLZlxcQ==}