@oneblink/apps-react 11.0.0-beta.1 → 11.0.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/apps/auth-service.d.ts +15 -8
  2. package/dist/apps/auth-service.js +18 -8
  3. package/dist/apps/auth-service.js.map +1 -1
  4. package/dist/apps/index.d.ts +10 -0
  5. package/dist/apps/index.js +10 -0
  6. package/dist/apps/index.js.map +1 -1
  7. package/dist/apps/mfa-service.d.ts +29 -0
  8. package/dist/apps/mfa-service.js +30 -0
  9. package/dist/apps/mfa-service.js.map +1 -0
  10. package/dist/apps/services/AWSCognitoClient.d.ts +17 -11
  11. package/dist/apps/services/AWSCognitoClient.js +67 -38
  12. package/dist/apps/services/AWSCognitoClient.js.map +1 -1
  13. package/dist/apps/services/cognito.d.ts +5 -24
  14. package/dist/apps/services/cognito.js +3 -32
  15. package/dist/apps/services/cognito.js.map +1 -1
  16. package/dist/components/mfa/MfaAuthenticatorAppDialog.js +2 -2
  17. package/dist/components/mfa/MfaAuthenticatorAppDialog.js.map +1 -1
  18. package/dist/components/mfa/MfaMethodRow.d.ts +4 -1
  19. package/dist/components/mfa/MfaMethodRow.js +5 -5
  20. package/dist/components/mfa/MfaMethodRow.js.map +1 -1
  21. package/dist/components/mfa/MfaPhoneNumberDialog.js +10 -35
  22. package/dist/components/mfa/MfaPhoneNumberDialog.js.map +1 -1
  23. package/dist/components/mfa/MultiFactorAuthentication.d.ts +12 -10
  24. package/dist/components/mfa/MultiFactorAuthentication.js +21 -14
  25. package/dist/components/mfa/MultiFactorAuthentication.js.map +1 -1
  26. package/dist/form-elements/FormElementBarcodeScanner.js +66 -18
  27. package/dist/form-elements/FormElementBarcodeScanner.js.map +1 -1
  28. package/dist/hooks/useLogin.d.ts +16 -2
  29. package/dist/hooks/useLogin.js +53 -3
  30. package/dist/hooks/useLogin.js.map +1 -1
  31. package/dist/hooks/useMfa.d.ts +66 -31
  32. package/dist/hooks/useMfa.js +84 -61
  33. package/dist/hooks/useMfa.js.map +1 -1
  34. package/dist/hooks/useResendCoolDown.d.ts +5 -0
  35. package/dist/hooks/useResendCoolDown.js +27 -0
  36. package/dist/hooks/useResendCoolDown.js.map +1 -0
  37. package/dist/index.d.ts +2 -0
  38. package/dist/index.js +2 -0
  39. package/dist/index.js.map +1 -1
  40. package/dist/styles/barcode-scanner.scss +4 -0
  41. package/dist/styles.css +5 -0
  42. package/dist/styles.scss +1 -0
  43. package/dist/utils/joinArray.d.ts +1 -0
  44. package/dist/utils/joinArray.js +7 -0
  45. package/dist/utils/joinArray.js.map +1 -0
  46. package/dist/utils/mfa-requirement.d.ts +12 -0
  47. package/dist/utils/mfa-requirement.js +149 -0
  48. package/dist/utils/mfa-requirement.js.map +1 -0
  49. package/package.json +3 -3
@@ -1,10 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { memo } from 'react';
3
- import { Box, Button, Chip, Grid, Tooltip, Typography } from '@mui/material';
4
- function MfaMethodRow({ isEnabled, isPreferred, isSettingUp, isSettingPreferredMfaMethod, isSetupDisabled, showSetupErrorTooltip, title, description, detail, cypressPrefix, extraButtons, onSetup, onDisable, onSetPreferred, }) {
5
- return (_jsx(Box, { "data-cypress": `${cypressPrefix}-method-row`, children: _jsxs(Grid, { container: true, spacing: 2, alignItems: "center", children: [_jsxs(Grid, { size: { xs: 'grow' }, children: [_jsxs(Box, { sx: { mb: 1 }, children: [_jsx(Typography, { variant: "subtitle1", component: "span", children: title }), isEnabled && (_jsx(Chip, { size: "small", label: "Enabled", color: "info", sx: { ml: 1 }, "data-cypress": `${cypressPrefix}-status-chip` })), isPreferred && (_jsx(Chip, { size: "small", label: "Preferred", color: "success", sx: { ml: 1 }, "data-cypress": `${cypressPrefix}-preferred-chip` }))] }), _jsx(Typography, { variant: "body2", color: "text.secondary", children: description }), !!detail && (_jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mt: 1 }, children: detail }))] }), _jsx(Grid, { size: "auto", children: _jsxs(Box, { display: "flex", gap: 1, flexWrap: "wrap", justifyContent: "flex-end", children: [extraButtons, isEnabled ? (_jsxs(_Fragment, { children: [!isPreferred && (_jsx(Button, { size: "small", variant: "outlined", loading: isSettingPreferredMfaMethod, disabled: isSettingPreferredMfaMethod, onClick: onSetPreferred, "data-cypress": `${cypressPrefix}-set-preferred-button`, children: "Set as Preferred" })), _jsx(Button, { size: "small", variant: "outlined", onClick: onDisable, "data-cypress": `${cypressPrefix}-disable-button`, children: "Disable" })] })) : (_jsx(Tooltip, { title: showSetupErrorTooltip
6
- ? 'We are unable to load your MFA status. Please try again by clicking the reload button on the chip above.'
7
- : '', children: _jsx("span", { children: _jsx(Button, { size: "small", variant: "contained", loading: isSettingUp, disabled: isSetupDisabled, onClick: onSetup, "data-cypress": `${cypressPrefix}-setup-button`, children: "Setup" }) }) }))] }) })] }) }));
3
+ import { Alert, Box, Button, Chip, Grid, Tooltip, Typography, } from '@mui/material';
4
+ function MfaMethodRow({ isEnabled, isPreferred, isUsedForLogin, isSettingUp, isSettingPreferredMfaMethod, isSetupDisabled, showSetupErrorTooltip, title, description, detail, mfaRequirementMessage, mfaRequirementMessageIsWarning, cypressPrefix, extraButtons, onSetup, onDisable, onSetPreferred, }) {
5
+ return (_jsxs(Box, { "data-cypress": `${cypressPrefix}-method-row`, children: [_jsxs(Grid, { container: true, spacing: 2, alignItems: "center", children: [_jsxs(Grid, { size: { xs: 'grow' }, children: [_jsxs(Box, { sx: { mb: 1 }, children: [_jsx(Typography, { variant: "subtitle1", component: "span", children: title }), isEnabled && (_jsx(Chip, { size: "small", label: "Enabled", color: "default", sx: { ml: 1 }, "data-cypress": `${cypressPrefix}-status-chip` })), isPreferred && (_jsx(Chip, { size: "small", label: "Preferred", color: "info", sx: { ml: 1 }, "data-cypress": `${cypressPrefix}-preferred-chip` })), isUsedForLogin && (_jsx(Chip, { size: "small", label: "Used for login", color: "success", sx: { ml: 1 }, "data-cypress": `${cypressPrefix}-used-for-login-chip` }))] }), _jsx(Typography, { variant: "body2", color: "text.secondary", children: description }), !!detail && (_jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mt: 1 }, children: detail }))] }), _jsx(Grid, { size: "auto", children: _jsxs(Box, { display: "flex", gap: 1, flexWrap: "wrap", justifyContent: "flex-end", children: [extraButtons, isEnabled ? (_jsxs(_Fragment, { children: [!isPreferred && (_jsx(Button, { size: "small", variant: "outlined", loading: isSettingPreferredMfaMethod, disabled: isSettingPreferredMfaMethod, onClick: onSetPreferred, "data-cypress": `${cypressPrefix}-set-preferred-button`, children: "Set as Preferred" })), _jsx(Button, { size: "small", variant: "outlined", onClick: onDisable, "data-cypress": `${cypressPrefix}-disable-button`, children: "Disable" })] })) : (_jsx(Tooltip, { title: showSetupErrorTooltip
6
+ ? 'We are unable to load your MFA status. Please try again by clicking the reload button on the chip above.'
7
+ : '', children: _jsx("span", { children: _jsx(Button, { size: "small", variant: "contained", loading: isSettingUp, disabled: isSetupDisabled, onClick: onSetup, "data-cypress": `${cypressPrefix}-setup-button`, children: "Setup" }) }) }))] }) })] }), !!mfaRequirementMessage && (_jsx(Alert, { severity: mfaRequirementMessageIsWarning ? 'warning' : 'info', "data-cypress": `${cypressPrefix}-mfa-requirement-message`, children: mfaRequirementMessage }))] }));
8
8
  }
9
9
  export default memo(MfaMethodRow);
10
10
  //# sourceMappingURL=MfaMethodRow.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MfaMethodRow.js","sourceRoot":"","sources":["../../../src/components/mfa/MfaMethodRow.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAC5B,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAmB5E,SAAS,YAAY,CAAC,EACpB,SAAS,EACT,WAAW,EACX,WAAW,EACX,2BAA2B,EAC3B,eAAe,EACf,qBAAqB,EACrB,KAAK,EACL,WAAW,EACX,MAAM,EACN,aAAa,EACb,YAAY,EACZ,OAAO,EACP,SAAS,EACT,cAAc,GACR;IACN,OAAO,CACL,KAAC,GAAG,oBAAe,GAAG,aAAa,aAAa,YAC9C,MAAC,IAAI,IAAC,SAAS,QAAC,OAAO,EAAE,CAAC,EAAE,UAAU,EAAC,QAAQ,aAC7C,MAAC,IAAI,IAAC,IAAI,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,aACxB,MAAC,GAAG,IAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,aAChB,KAAC,UAAU,IAAC,OAAO,EAAC,WAAW,EAAC,SAAS,EAAC,MAAM,YAC7C,KAAK,GACK,EACZ,SAAS,IAAI,CACZ,KAAC,IAAI,IACH,IAAI,EAAC,OAAO,EACZ,KAAK,EAAC,SAAS,EACf,KAAK,EAAC,MAAM,EACZ,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,kBACC,GAAG,aAAa,cAAc,GAC5C,CACH,EACA,WAAW,IAAI,CACd,KAAC,IAAI,IACH,IAAI,EAAC,OAAO,EACZ,KAAK,EAAC,WAAW,EACjB,KAAK,EAAC,SAAS,EACf,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,kBACC,GAAG,aAAa,iBAAiB,GAC/C,CACH,IACG,EACN,KAAC,UAAU,IAAC,OAAO,EAAC,OAAO,EAAC,KAAK,EAAC,gBAAgB,YAC/C,WAAW,GACD,EACZ,CAAC,CAAC,MAAM,IAAI,CACX,KAAC,UAAU,IAAC,OAAO,EAAC,OAAO,EAAC,KAAK,EAAC,gBAAgB,EAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,YAC7D,MAAM,GACI,CACd,IACI,EACP,KAAC,IAAI,IAAC,IAAI,EAAC,MAAM,YACf,MAAC,GAAG,IAAC,OAAO,EAAC,MAAM,EAAC,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAC,MAAM,EAAC,cAAc,EAAC,UAAU,aAClE,YAAY,EACZ,SAAS,CAAC,CAAC,CAAC,CACX,8BACG,CAAC,WAAW,IAAI,CACf,KAAC,MAAM,IACL,IAAI,EAAC,OAAO,EACZ,OAAO,EAAC,UAAU,EAClB,OAAO,EAAE,2BAA2B,EACpC,QAAQ,EAAE,2BAA2B,EACrC,OAAO,EAAE,cAAc,kBACT,GAAG,aAAa,uBAAuB,iCAG9C,CACV,EACD,KAAC,MAAM,IACL,IAAI,EAAC,OAAO,EACZ,OAAO,EAAC,UAAU,EAClB,OAAO,EAAE,SAAS,kBACJ,GAAG,aAAa,iBAAiB,wBAGxC,IACR,CACJ,CAAC,CAAC,CAAC,CACF,KAAC,OAAO,IACN,KAAK,EACH,qBAAqB;oCACnB,CAAC,CAAC,0GAA0G;oCAC5G,CAAC,CAAC,EAAE,YAGR,yBACE,KAAC,MAAM,IACL,IAAI,EAAC,OAAO,EACZ,OAAO,EAAC,WAAW,EACnB,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,OAAO,kBACF,GAAG,aAAa,eAAe,sBAGtC,GACJ,GACC,CACX,IACG,GACD,IACF,GACH,CACP,CAAA;AACH,CAAC;AAED,eAAe,IAAI,CAAC,YAAY,CAAC,CAAA","sourcesContent":["import { memo } from 'react'\nimport { Box, Button, Chip, Grid, Tooltip, Typography } from '@mui/material'\n\ntype Props = {\n isEnabled: boolean\n isPreferred: boolean\n isSettingUp: boolean\n isSettingPreferredMfaMethod: boolean\n isSetupDisabled: boolean\n showSetupErrorTooltip?: boolean\n title: string\n description: string\n detail?: string\n cypressPrefix: string\n extraButtons?: React.ReactNode\n onSetup: () => void\n onDisable: () => void\n onSetPreferred: () => void\n}\n\nfunction MfaMethodRow({\n isEnabled,\n isPreferred,\n isSettingUp,\n isSettingPreferredMfaMethod,\n isSetupDisabled,\n showSetupErrorTooltip,\n title,\n description,\n detail,\n cypressPrefix,\n extraButtons,\n onSetup,\n onDisable,\n onSetPreferred,\n}: Props) {\n return (\n <Box data-cypress={`${cypressPrefix}-method-row`}>\n <Grid container spacing={2} alignItems=\"center\">\n <Grid size={{ xs: 'grow' }}>\n <Box sx={{ mb: 1 }}>\n <Typography variant=\"subtitle1\" component=\"span\">\n {title}\n </Typography>\n {isEnabled && (\n <Chip\n size=\"small\"\n label=\"Enabled\"\n color=\"info\"\n sx={{ ml: 1 }}\n data-cypress={`${cypressPrefix}-status-chip`}\n />\n )}\n {isPreferred && (\n <Chip\n size=\"small\"\n label=\"Preferred\"\n color=\"success\"\n sx={{ ml: 1 }}\n data-cypress={`${cypressPrefix}-preferred-chip`}\n />\n )}\n </Box>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {description}\n </Typography>\n {!!detail && (\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ mt: 1 }}>\n {detail}\n </Typography>\n )}\n </Grid>\n <Grid size=\"auto\">\n <Box display=\"flex\" gap={1} flexWrap=\"wrap\" justifyContent=\"flex-end\">\n {extraButtons}\n {isEnabled ? (\n <>\n {!isPreferred && (\n <Button\n size=\"small\"\n variant=\"outlined\"\n loading={isSettingPreferredMfaMethod}\n disabled={isSettingPreferredMfaMethod}\n onClick={onSetPreferred}\n data-cypress={`${cypressPrefix}-set-preferred-button`}\n >\n Set as Preferred\n </Button>\n )}\n <Button\n size=\"small\"\n variant=\"outlined\"\n onClick={onDisable}\n data-cypress={`${cypressPrefix}-disable-button`}\n >\n Disable\n </Button>\n </>\n ) : (\n <Tooltip\n title={\n showSetupErrorTooltip\n ? 'We are unable to load your MFA status. Please try again by clicking the reload button on the chip above.'\n : ''\n }\n >\n <span>\n <Button\n size=\"small\"\n variant=\"contained\"\n loading={isSettingUp}\n disabled={isSetupDisabled}\n onClick={onSetup}\n data-cypress={`${cypressPrefix}-setup-button`}\n >\n Setup\n </Button>\n </span>\n </Tooltip>\n )}\n </Box>\n </Grid>\n </Grid>\n </Box>\n )\n}\n\nexport default memo(MfaMethodRow)\n"]}
1
+ {"version":3,"file":"MfaMethodRow.js","sourceRoot":"","sources":["../../../src/components/mfa/MfaMethodRow.tsx"],"names":[],"mappings":"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","sourcesContent":["import { memo } from 'react'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n Grid,\n Tooltip,\n Typography,\n} from '@mui/material'\n\ntype Props = {\n isEnabled: boolean\n isPreferred: boolean\n isUsedForLogin: boolean\n isSettingUp: boolean\n isSettingPreferredMfaMethod: boolean\n isSetupDisabled: boolean\n showSetupErrorTooltip?: boolean\n title: string\n description: string\n detail?: string\n mfaRequirementMessage?: string\n mfaRequirementMessageIsWarning?: boolean\n cypressPrefix: string\n extraButtons?: React.ReactNode\n onSetup: () => void\n onDisable: () => void\n onSetPreferred: () => void\n}\n\nfunction MfaMethodRow({\n isEnabled,\n isPreferred,\n isUsedForLogin,\n isSettingUp,\n isSettingPreferredMfaMethod,\n isSetupDisabled,\n showSetupErrorTooltip,\n title,\n description,\n detail,\n mfaRequirementMessage,\n mfaRequirementMessageIsWarning,\n cypressPrefix,\n extraButtons,\n onSetup,\n onDisable,\n onSetPreferred,\n}: Props) {\n return (\n <Box data-cypress={`${cypressPrefix}-method-row`}>\n <Grid container spacing={2} alignItems=\"center\">\n <Grid size={{ xs: 'grow' }}>\n <Box sx={{ mb: 1 }}>\n <Typography variant=\"subtitle1\" component=\"span\">\n {title}\n </Typography>\n {isEnabled && (\n <Chip\n size=\"small\"\n label=\"Enabled\"\n color=\"default\"\n sx={{ ml: 1 }}\n data-cypress={`${cypressPrefix}-status-chip`}\n />\n )}\n {isPreferred && (\n <Chip\n size=\"small\"\n label=\"Preferred\"\n color=\"info\"\n sx={{ ml: 1 }}\n data-cypress={`${cypressPrefix}-preferred-chip`}\n />\n )}\n {isUsedForLogin && (\n <Chip\n size=\"small\"\n label=\"Used for login\"\n color=\"success\"\n sx={{ ml: 1 }}\n data-cypress={`${cypressPrefix}-used-for-login-chip`}\n />\n )}\n </Box>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {description}\n </Typography>\n {!!detail && (\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ mt: 1 }}>\n {detail}\n </Typography>\n )}\n </Grid>\n <Grid size=\"auto\">\n <Box display=\"flex\" gap={1} flexWrap=\"wrap\" justifyContent=\"flex-end\">\n {extraButtons}\n {isEnabled ? (\n <>\n {!isPreferred && (\n <Button\n size=\"small\"\n variant=\"outlined\"\n loading={isSettingPreferredMfaMethod}\n disabled={isSettingPreferredMfaMethod}\n onClick={onSetPreferred}\n data-cypress={`${cypressPrefix}-set-preferred-button`}\n >\n Set as Preferred\n </Button>\n )}\n <Button\n size=\"small\"\n variant=\"outlined\"\n onClick={onDisable}\n data-cypress={`${cypressPrefix}-disable-button`}\n >\n Disable\n </Button>\n </>\n ) : (\n <Tooltip\n title={\n showSetupErrorTooltip\n ? 'We are unable to load your MFA status. Please try again by clicking the reload button on the chip above.'\n : ''\n }\n >\n <span>\n <Button\n size=\"small\"\n variant=\"contained\"\n loading={isSettingUp}\n disabled={isSetupDisabled}\n onClick={onSetup}\n data-cypress={`${cypressPrefix}-setup-button`}\n >\n Setup\n </Button>\n </span>\n </Tooltip>\n )}\n </Box>\n </Grid>\n </Grid>\n\n {!!mfaRequirementMessage && (\n <Alert\n severity={mfaRequirementMessageIsWarning ? 'warning' : 'info'}\n data-cypress={`${cypressPrefix}-mfa-requirement-message`}\n >\n {mfaRequirementMessage}\n </Alert>\n )}\n </Box>\n )\n}\n\nexport default memo(MfaMethodRow)\n"]}
@@ -3,41 +3,16 @@ import * as React from 'react';
3
3
  import { Dialog, DialogActions, DialogContent, DialogTitle, Button, Typography, Box, } from '@mui/material';
4
4
  import useBooleanState from '../../hooks/useBooleanState';
5
5
  import useMfa from '../../hooks/useMfa';
6
+ import useResendCoolDown from '../../hooks/useResendCoolDown';
6
7
  import InputField from '../InputField';
7
8
  const PHONE_VERIFICATION_RESEND_COOL_DOWN_SECONDS = 60;
8
- function getResendCoolDownRemainingSeconds(sentAt, now) {
9
- if (!sentAt) {
10
- return 0;
11
- }
12
- const elapsedSeconds = Math.floor((now - sentAt) / 1000);
13
- return Math.max(0, PHONE_VERIFICATION_RESEND_COOL_DOWN_SECONDS - elapsedSeconds);
14
- }
15
- function usePhoneVerificationResendCoolDown(sentAt) {
16
- const [now, setNow] = React.useState(() => Date.now());
17
- const remainingSeconds = getResendCoolDownRemainingSeconds(sentAt, now);
18
- React.useEffect(() => {
19
- if (!sentAt) {
20
- return;
21
- }
22
- const remaining = getResendCoolDownRemainingSeconds(sentAt, Date.now());
23
- if (remaining <= 0) {
24
- return;
25
- }
26
- const intervalId = window.setInterval(() => {
27
- setNow(Date.now());
28
- }, 1000);
29
- return () => window.clearInterval(intervalId);
30
- }, [sentAt]);
31
- return remainingSeconds;
32
- }
33
9
  function MfaPhoneNumberDialog() {
34
- const { isPhoneNumberDialogOpen, mfaSettings, phoneVerificationCodeSentAt, closePhoneNumberDialog, savePhoneNumber, verifyPhoneNumber, resendPhoneNumberVerificationCode, } = useMfa();
10
+ const { isPhoneNumberDialogOpen, mfaSettings, phoneVerificationCodeSentAt, closePhoneNumberDialog, savePhoneNumber, verifyPhoneNumber, } = useMfa();
35
11
  const isPhoneVerificationRequired = phoneVerificationCodeSentAt !== undefined;
36
12
  const [phoneNumber, setPhoneNumber] = React.useState('');
37
13
  const [verificationCode, setVerificationCode] = React.useState('');
38
- const resendCoolDownSeconds = usePhoneVerificationResendCoolDown(phoneVerificationCodeSentAt);
14
+ const resendCoolDownSeconds = useResendCoolDown(phoneVerificationCodeSentAt, PHONE_VERIFICATION_RESEND_COOL_DOWN_SECONDS);
39
15
  const [isSaving, startSaving, stopSaving] = useBooleanState(false);
40
- const [isResending, startResending, stopResending] = useBooleanState(false);
41
16
  React.useEffect(() => {
42
17
  if (isPhoneNumberDialogOpen) {
43
18
  setPhoneNumber(mfaSettings.sms.phoneNumber || '');
@@ -87,14 +62,14 @@ function MfaPhoneNumberDialog() {
87
62
  verificationCode,
88
63
  ]);
89
64
  const handleResendVerificationCode = React.useCallback(async () => {
90
- startResending();
65
+ startSaving();
91
66
  try {
92
- await resendPhoneNumberVerificationCode();
67
+ await savePhoneNumber(phoneNumber);
93
68
  }
94
69
  finally {
95
- stopResending();
70
+ stopSaving();
96
71
  }
97
- }, [resendPhoneNumberVerificationCode, startResending, stopResending]);
72
+ }, [phoneNumber, savePhoneNumber, startSaving, stopSaving]);
98
73
  return (_jsx(Dialog, { open: isPhoneNumberDialogOpen, onClose: () => {
99
74
  if (!isSaving) {
100
75
  closePhoneNumberDialog();
@@ -103,9 +78,9 @@ function MfaPhoneNumberDialog() {
103
78
  ? 'Verify Phone Number'
104
79
  : 'Save Phone Number' }), _jsx(DialogContent, { dividers: true, children: isPhoneVerificationRequired ? (_jsxs(_Fragment, { children: [_jsxs(Typography, { variant: "body2", paragraph: true, children: ["Enter the verification code sent to ", phoneNumber, "."] }), _jsx(InputField, { autoFocus: true, margin: "none", name: "verificationCode", label: "Verification Code", fullWidth: true, placeholder: "XXXXXX", variant: "outlined", value: verificationCode, onChange: (event) => {
105
80
  setVerificationCode(event.target.value);
106
- }, disabled: isSaving, "data-cypress": "mfa-phone-verification-code" }, "verification-code"), _jsx(Box, { marginTop: 1, children: _jsx(Button, { type: "button", variant: "text", size: "small", disabled: resendCoolDownSeconds > 0 || isSaving || isResending, onClick: handleResendVerificationCode, "data-cypress": "mfa-phone-resend-button", children: resendCoolDownSeconds > 0
107
- ? `Send again (${resendCoolDownSeconds}s)`
108
- : 'Send again' }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Typography, { variant: "body2", paragraph: true, children: "Enter your phone number to receive SMS verification codes when signing in." }), _jsx(InputField, { autoFocus: true, margin: "none", name: "phoneNumber", label: "Phone Number", fullWidth: true, placeholder: "+61400000000", variant: "outlined", value: phoneNumber, onChange: (event) => {
81
+ }, disabled: isSaving, "data-cypress": "mfa-phone-verification-code" }, "verification-code"), _jsx(Box, { marginTop: 1, children: _jsx(Button, { type: "button", variant: "text", size: "small", sx: { textTransform: 'none' }, disabled: resendCoolDownSeconds > 0 || isSaving, onClick: handleResendVerificationCode, "data-cypress": "mfa-phone-resend-button", children: resendCoolDownSeconds > 0
82
+ ? `Send Again (${resendCoolDownSeconds}s)`
83
+ : 'Send Again' }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Typography, { variant: "body2", paragraph: true, children: "Enter your phone number to receive SMS verification codes when signing in." }), _jsx(InputField, { autoFocus: true, margin: "none", name: "phoneNumber", label: "Phone Number", fullWidth: true, placeholder: "+61400000000", variant: "outlined", value: phoneNumber, onChange: (event) => {
109
84
  setPhoneNumber(event.target.value);
110
85
  }, disabled: isSaving, helperText: "Include your country code, for example +61400000000.", "data-cypress": "mfa-phone-number" }, "phone-number")] })) }), _jsxs(DialogActions, { children: [_jsx(Button, { type: "button", onClick: closePhoneNumberDialog, disabled: isSaving, children: "Cancel" }), isPhoneVerificationRequired ? (_jsx(Button, { type: "submit", variant: "contained", disabled: !verificationCode || isSaving, "data-cypress": "mfa-phone-verify-button", children: "Verify" })) : (_jsx(Button, { type: "submit", variant: "contained", disabled: !phoneNumber || isSaving, "data-cypress": "mfa-phone-save-button", children: "Save" }))] })] }) }));
111
86
  }
@@ -1 +1 @@
1
- {"version":3,"file":"MfaPhoneNumberDialog.js","sourceRoot":"","sources":["../../../src/components/mfa/MfaPhoneNumberDialog.tsx"],"names":[],"mappings":"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","sourcesContent":["import * as React from 'react'\nimport {\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Button,\n Typography,\n Box,\n} from '@mui/material'\nimport useBooleanState from '../../hooks/useBooleanState'\nimport useMfa from '../../hooks/useMfa'\nimport InputField from '../InputField'\n\nconst PHONE_VERIFICATION_RESEND_COOL_DOWN_SECONDS = 60\n\nfunction getResendCoolDownRemainingSeconds(\n sentAt: number | undefined,\n now: number,\n) {\n if (!sentAt) {\n return 0\n }\n\n const elapsedSeconds = Math.floor((now - sentAt) / 1000)\n return Math.max(\n 0,\n PHONE_VERIFICATION_RESEND_COOL_DOWN_SECONDS - elapsedSeconds,\n )\n}\n\nfunction usePhoneVerificationResendCoolDown(sentAt: number | undefined) {\n const [now, setNow] = React.useState(() => Date.now())\n\n const remainingSeconds = getResendCoolDownRemainingSeconds(sentAt, now)\n\n React.useEffect(() => {\n if (!sentAt) {\n return\n }\n\n const remaining = getResendCoolDownRemainingSeconds(sentAt, Date.now())\n if (remaining <= 0) {\n return\n }\n\n const intervalId = window.setInterval(() => {\n setNow(Date.now())\n }, 1000)\n\n return () => window.clearInterval(intervalId)\n }, [sentAt])\n\n return remainingSeconds\n}\n\nfunction MfaPhoneNumberDialog() {\n const {\n isPhoneNumberDialogOpen,\n mfaSettings,\n phoneVerificationCodeSentAt,\n closePhoneNumberDialog,\n savePhoneNumber,\n verifyPhoneNumber,\n resendPhoneNumberVerificationCode,\n } = useMfa()\n\n const isPhoneVerificationRequired = phoneVerificationCodeSentAt !== undefined\n\n const [phoneNumber, setPhoneNumber] = React.useState('')\n const [verificationCode, setVerificationCode] = React.useState('')\n const resendCoolDownSeconds = usePhoneVerificationResendCoolDown(\n phoneVerificationCodeSentAt,\n )\n const [isSaving, startSaving, stopSaving] = useBooleanState(false)\n const [isResending, startResending, stopResending] = useBooleanState(false)\n\n React.useEffect(() => {\n if (isPhoneNumberDialogOpen) {\n setPhoneNumber(mfaSettings.sms.phoneNumber || '')\n setVerificationCode('')\n }\n }, [isPhoneNumberDialogOpen, mfaSettings.sms.phoneNumber])\n\n const handleSavePhoneNumber = React.useCallback(async () => {\n startSaving()\n try {\n await savePhoneNumber(phoneNumber)\n } finally {\n stopSaving()\n }\n }, [phoneNumber, savePhoneNumber, startSaving, stopSaving])\n\n const handleVerifyPhoneNumber = React.useCallback(async () => {\n startSaving()\n try {\n await verifyPhoneNumber(verificationCode)\n } finally {\n stopSaving()\n }\n }, [verificationCode, verifyPhoneNumber, startSaving, stopSaving])\n\n const handleSubmit = React.useCallback(\n (event: React.SubmitEvent<HTMLFormElement>) => {\n event.preventDefault()\n\n if (isSaving) {\n return\n }\n\n if (isPhoneVerificationRequired) {\n if (!verificationCode) {\n return\n }\n\n void handleVerifyPhoneNumber()\n return\n }\n\n if (!phoneNumber) {\n return\n }\n\n void handleSavePhoneNumber()\n },\n [\n handleSavePhoneNumber,\n handleVerifyPhoneNumber,\n isPhoneVerificationRequired,\n isSaving,\n phoneNumber,\n verificationCode,\n ],\n )\n\n const handleResendVerificationCode = React.useCallback(async () => {\n startResending()\n try {\n await resendPhoneNumberVerificationCode()\n } finally {\n stopResending()\n }\n }, [resendPhoneNumberVerificationCode, startResending, stopResending])\n\n return (\n <Dialog\n open={isPhoneNumberDialogOpen}\n onClose={() => {\n if (!isSaving) {\n closePhoneNumberDialog()\n }\n }}\n fullWidth\n maxWidth=\"sm\"\n >\n <form onSubmit={handleSubmit}>\n <DialogTitle>\n {isPhoneVerificationRequired\n ? 'Verify Phone Number'\n : 'Save Phone Number'}\n </DialogTitle>\n <DialogContent dividers>\n {isPhoneVerificationRequired ? (\n <>\n <Typography variant=\"body2\" paragraph>\n Enter the verification code sent to {phoneNumber}.\n </Typography>\n <InputField\n key=\"verification-code\"\n autoFocus\n margin=\"none\"\n name=\"verificationCode\"\n label=\"Verification Code\"\n fullWidth\n placeholder=\"XXXXXX\"\n variant=\"outlined\"\n value={verificationCode}\n onChange={(event) => {\n setVerificationCode(event.target.value)\n }}\n disabled={isSaving}\n data-cypress=\"mfa-phone-verification-code\"\n />\n <Box marginTop={1}>\n <Button\n type=\"button\"\n variant=\"text\"\n size=\"small\"\n disabled={\n resendCoolDownSeconds > 0 || isSaving || isResending\n }\n onClick={handleResendVerificationCode}\n data-cypress=\"mfa-phone-resend-button\"\n >\n {resendCoolDownSeconds > 0\n ? `Send again (${resendCoolDownSeconds}s)`\n : 'Send again'}\n </Button>\n </Box>\n </>\n ) : (\n <>\n <Typography variant=\"body2\" paragraph>\n Enter your phone number to receive SMS verification codes when\n signing in.\n </Typography>\n <InputField\n key=\"phone-number\"\n autoFocus\n margin=\"none\"\n name=\"phoneNumber\"\n label=\"Phone Number\"\n fullWidth\n placeholder=\"+61400000000\"\n variant=\"outlined\"\n value={phoneNumber}\n onChange={(event) => {\n setPhoneNumber(event.target.value)\n }}\n disabled={isSaving}\n helperText=\"Include your country code, for example +61400000000.\"\n data-cypress=\"mfa-phone-number\"\n />\n </>\n )}\n </DialogContent>\n <DialogActions>\n <Button\n type=\"button\"\n onClick={closePhoneNumberDialog}\n disabled={isSaving}\n >\n Cancel\n </Button>\n {isPhoneVerificationRequired ? (\n <Button\n type=\"submit\"\n variant=\"contained\"\n disabled={!verificationCode || isSaving}\n data-cypress=\"mfa-phone-verify-button\"\n >\n Verify\n </Button>\n ) : (\n <Button\n type=\"submit\"\n variant=\"contained\"\n disabled={!phoneNumber || isSaving}\n data-cypress=\"mfa-phone-save-button\"\n >\n Save\n </Button>\n )}\n </DialogActions>\n </form>\n </Dialog>\n )\n}\n\n/**\n * React Component that lets users enter and verify a phone number for SMS MFA.\n * Typically rendered by `<MultiFactorAuthentication />` within an `<MfaProvider\n * />` tree.\n *\n * @returns\n */\nexport default React.memo(MfaPhoneNumberDialog)\n"]}
1
+ {"version":3,"file":"MfaPhoneNumberDialog.js","sourceRoot":"","sources":["../../../src/components/mfa/MfaPhoneNumberDialog.tsx"],"names":[],"mappings":"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","sourcesContent":["import * as React from 'react'\nimport {\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Button,\n Typography,\n Box,\n} from '@mui/material'\nimport useBooleanState from '../../hooks/useBooleanState'\nimport useMfa from '../../hooks/useMfa'\nimport useResendCoolDown from '../../hooks/useResendCoolDown'\nimport InputField from '../InputField'\n\nconst PHONE_VERIFICATION_RESEND_COOL_DOWN_SECONDS = 60\n\nfunction MfaPhoneNumberDialog() {\n const {\n isPhoneNumberDialogOpen,\n mfaSettings,\n phoneVerificationCodeSentAt,\n closePhoneNumberDialog,\n savePhoneNumber,\n verifyPhoneNumber,\n } = useMfa()\n\n const isPhoneVerificationRequired = phoneVerificationCodeSentAt !== undefined\n\n const [phoneNumber, setPhoneNumber] = React.useState('')\n const [verificationCode, setVerificationCode] = React.useState('')\n const resendCoolDownSeconds = useResendCoolDown(\n phoneVerificationCodeSentAt,\n PHONE_VERIFICATION_RESEND_COOL_DOWN_SECONDS,\n )\n const [isSaving, startSaving, stopSaving] = useBooleanState(false)\n\n React.useEffect(() => {\n if (isPhoneNumberDialogOpen) {\n setPhoneNumber(mfaSettings.sms.phoneNumber || '')\n setVerificationCode('')\n }\n }, [isPhoneNumberDialogOpen, mfaSettings.sms.phoneNumber])\n\n const handleSavePhoneNumber = React.useCallback(async () => {\n startSaving()\n try {\n await savePhoneNumber(phoneNumber)\n } finally {\n stopSaving()\n }\n }, [phoneNumber, savePhoneNumber, startSaving, stopSaving])\n\n const handleVerifyPhoneNumber = React.useCallback(async () => {\n startSaving()\n try {\n await verifyPhoneNumber(verificationCode)\n } finally {\n stopSaving()\n }\n }, [verificationCode, verifyPhoneNumber, startSaving, stopSaving])\n\n const handleSubmit = React.useCallback(\n (event: React.SubmitEvent<HTMLFormElement>) => {\n event.preventDefault()\n\n if (isSaving) {\n return\n }\n\n if (isPhoneVerificationRequired) {\n if (!verificationCode) {\n return\n }\n\n void handleVerifyPhoneNumber()\n return\n }\n\n if (!phoneNumber) {\n return\n }\n\n void handleSavePhoneNumber()\n },\n [\n handleSavePhoneNumber,\n handleVerifyPhoneNumber,\n isPhoneVerificationRequired,\n isSaving,\n phoneNumber,\n verificationCode,\n ],\n )\n\n const handleResendVerificationCode = React.useCallback(async () => {\n startSaving()\n try {\n await savePhoneNumber(phoneNumber)\n } finally {\n stopSaving()\n }\n }, [phoneNumber, savePhoneNumber, startSaving, stopSaving])\n\n return (\n <Dialog\n open={isPhoneNumberDialogOpen}\n onClose={() => {\n if (!isSaving) {\n closePhoneNumberDialog()\n }\n }}\n fullWidth\n maxWidth=\"sm\"\n >\n <form onSubmit={handleSubmit}>\n <DialogTitle>\n {isPhoneVerificationRequired\n ? 'Verify Phone Number'\n : 'Save Phone Number'}\n </DialogTitle>\n <DialogContent dividers>\n {isPhoneVerificationRequired ? (\n <>\n <Typography variant=\"body2\" paragraph>\n Enter the verification code sent to {phoneNumber}.\n </Typography>\n <InputField\n key=\"verification-code\"\n autoFocus\n margin=\"none\"\n name=\"verificationCode\"\n label=\"Verification Code\"\n fullWidth\n placeholder=\"XXXXXX\"\n variant=\"outlined\"\n value={verificationCode}\n onChange={(event) => {\n setVerificationCode(event.target.value)\n }}\n disabled={isSaving}\n data-cypress=\"mfa-phone-verification-code\"\n />\n <Box marginTop={1}>\n <Button\n type=\"button\"\n variant=\"text\"\n size=\"small\"\n sx={{ textTransform: 'none' }}\n disabled={resendCoolDownSeconds > 0 || isSaving}\n onClick={handleResendVerificationCode}\n data-cypress=\"mfa-phone-resend-button\"\n >\n {resendCoolDownSeconds > 0\n ? `Send Again (${resendCoolDownSeconds}s)`\n : 'Send Again'}\n </Button>\n </Box>\n </>\n ) : (\n <>\n <Typography variant=\"body2\" paragraph>\n Enter your phone number to receive SMS verification codes when\n signing in.\n </Typography>\n <InputField\n key=\"phone-number\"\n autoFocus\n margin=\"none\"\n name=\"phoneNumber\"\n label=\"Phone Number\"\n fullWidth\n placeholder=\"+61400000000\"\n variant=\"outlined\"\n value={phoneNumber}\n onChange={(event) => {\n setPhoneNumber(event.target.value)\n }}\n disabled={isSaving}\n helperText=\"Include your country code, for example +61400000000.\"\n data-cypress=\"mfa-phone-number\"\n />\n </>\n )}\n </DialogContent>\n <DialogActions>\n <Button\n type=\"button\"\n onClick={closePhoneNumberDialog}\n disabled={isSaving}\n >\n Cancel\n </Button>\n {isPhoneVerificationRequired ? (\n <Button\n type=\"submit\"\n variant=\"contained\"\n disabled={!verificationCode || isSaving}\n data-cypress=\"mfa-phone-verify-button\"\n >\n Verify\n </Button>\n ) : (\n <Button\n type=\"submit\"\n variant=\"contained\"\n disabled={!phoneNumber || isSaving}\n data-cypress=\"mfa-phone-save-button\"\n >\n Save\n </Button>\n )}\n </DialogActions>\n </form>\n </Dialog>\n )\n}\n\n/**\n * React Component that lets users enter and verify a phone number for SMS MFA.\n * Typically rendered by `<MultiFactorAuthentication />` within an `<MfaProvider\n * />` tree.\n *\n * @returns\n */\nexport default React.memo(MfaPhoneNumberDialog)\n"]}
@@ -1,5 +1,8 @@
1
+ import { MiscTypes } from '@oneblink/types';
1
2
  type Props = {
2
- ssoSetupUrl: string | undefined;
3
+ mfaRequirement: MiscTypes.MfaRequirement | undefined;
4
+ ssoSetupUrl?: string;
5
+ accessScopeLabel?: string;
3
6
  };
4
7
  /**
5
8
  * React Component that provides a mechanism for app users to configure Multi
@@ -16,26 +19,25 @@ type Props = {
16
19
  * } from '@oneblink/apps-react'
17
20
  *
18
21
  * function Component() {
19
- * return <MultiFactorAuthentication />
22
+ * return <MultiFactorAuthentication mfaRequirement={undefined} />
20
23
  * }
21
24
  *
22
- * function App() {
25
+ * function AppWithMfaRequirement({ mfaRequirement }) {
23
26
  * return (
24
27
  * <MfaProvider>
25
- * <Component />
28
+ * <MultiFactorAuthentication mfaRequirement={mfaRequirement} />
26
29
  * </MfaProvider>
27
30
  * )
28
31
  * }
29
- *
30
- * const root = document.getElementById('root')
31
- * if (root) {
32
- * ReactDOM.render(<App />, root)
33
- * }
34
32
  * ```
35
33
  *
36
34
  * @param props
35
+ * @param props.mfaRequirement - The MFA methods allowed by your administrator
36
+ * for using this app. Pass `undefined` when the app has no MFA requirement.
37
+ * Users can still enable other methods, but will be warned when their
38
+ * configuration does not meet this requirement.
37
39
  * @returns
38
40
  * @group Components
39
41
  */
40
- export default function MultiFactorAuthentication({ ssoSetupUrl }: Props): import("react/jsx-runtime").JSX.Element;
42
+ export default function MultiFactorAuthentication({ mfaRequirement, ssoSetupUrl, accessScopeLabel, }: Props): import("react/jsx-runtime").JSX.Element;
41
43
  export {};
@@ -10,7 +10,8 @@ import MfaMethodRow from './MfaMethodRow';
10
10
  import MfaSuccessSnackbar from './MfaSuccessSnackbar';
11
11
  import MfaErrorSnackbar from './MfaErrorSnackbar';
12
12
  import MfaStatusChip from './MfaStatusChip';
13
- function MfaMethodList() {
13
+ import { formatMfaMethodRequirementMessage, isMfaMethodUsedForLogin, } from '../../utils/mfa-requirement';
14
+ function MfaMethodList({ mfaRequirement, accessScopeLabel = 'app', }) {
14
15
  const { mfaSettings, loadingError, isLoading, isSettingUpMfa, settingUpMfaMethod, isSettingPreferredMfaMethod, beginMfaSetup, beginDisablingMfaMethod, setPreferredMfaMethod, beginRemovingPhoneNumber, } = useMfa();
15
16
  const phoneDetail = useMemo(() => {
16
17
  if (!mfaSettings.sms.phoneNumber) {
@@ -21,15 +22,22 @@ function MfaMethodList() {
21
22
  }
22
23
  return `Phone number: ${mfaSettings.sms.phoneNumber}`;
23
24
  }, [mfaSettings]);
25
+ const authenticatorMfaRequirementMessage = useMemo(() => {
26
+ return formatMfaMethodRequirementMessage('authenticatorApp', mfaRequirement, mfaSettings, accessScopeLabel);
27
+ }, [accessScopeLabel, mfaRequirement, mfaSettings]);
28
+ const smsMfaRequirementMessage = useMemo(() => {
29
+ return formatMfaMethodRequirementMessage('sms', mfaRequirement, mfaSettings, accessScopeLabel);
30
+ }, [accessScopeLabel, mfaRequirement, mfaSettings]);
24
31
  if (isLoading) {
25
32
  return (_jsx(Typography, { variant: "body2", color: "text.secondary", children: "Loading MFA methods..." }));
26
33
  }
27
34
  if (loadingError) {
28
35
  return (_jsx(Typography, { variant: "body2", color: "error", children: "We are unable to load your MFA methods. Please try again by clicking the reload button on the chip above." }));
29
36
  }
30
- return (_jsxs(_Fragment, { children: [_jsx(MfaMethodRow, { isEnabled: mfaSettings.authenticator.enabled, isPreferred: mfaSettings.authenticator.preferred, isSettingUp: isSettingUpMfa && settingUpMfaMethod === 'authenticator', isSettingPreferredMfaMethod: isSettingPreferredMfaMethod, isSetupDisabled: !!loadingError || isSettingUpMfa, showSetupErrorTooltip: !!loadingError, title: "Authenticator App", description: "Use an app like Google Authenticator or Microsoft Authenticator to generate 6-digit verification codes.", cypressPrefix: "mfa-authenticator", onSetup: () => beginMfaSetup('authenticator'), onDisable: () => beginDisablingMfaMethod('authenticator'), onSetPreferred: () => setPreferredMfaMethod('authenticator') }), _jsx(Divider, { sx: { my: 2 } }), _jsx(MfaMethodRow, { isEnabled: mfaSettings.sms.enabled, isPreferred: mfaSettings.sms.preferred, isSettingUp: isSettingUpMfa && settingUpMfaMethod === 'sms', isSettingPreferredMfaMethod: isSettingPreferredMfaMethod, isSetupDisabled: !!loadingError || isSettingUpMfa, showSetupErrorTooltip: !!loadingError, title: "SMS", description: "Receive a one-time verification code via SMS each time MFA is required.", detail: phoneDetail, cypressPrefix: "mfa-sms", onSetup: () => beginMfaSetup('sms'), onDisable: () => beginDisablingMfaMethod('sms'), onSetPreferred: () => setPreferredMfaMethod('sms'), extraButtons: !!mfaSettings.sms.phoneNumber && !mfaSettings.sms.enabled ? (_jsx(Button, { size: "small", variant: "outlined", disabled: !!loadingError, onClick: beginRemovingPhoneNumber, "data-cypress": "mfa-sms-remove-phone-button", children: "Remove Phone" })) : undefined })] }));
37
+ return (_jsxs(_Fragment, { children: [_jsx(MfaMethodRow, { isEnabled: mfaSettings.authenticator.enabled, isPreferred: mfaSettings.authenticator.preferred, isUsedForLogin: isMfaMethodUsedForLogin(mfaSettings, 'authenticatorApp'), isSettingUp: isSettingUpMfa && settingUpMfaMethod === 'authenticator', isSettingPreferredMfaMethod: isSettingPreferredMfaMethod, isSetupDisabled: !!loadingError || isSettingUpMfa, showSetupErrorTooltip: !!loadingError, title: "Authenticator App", description: "Use an app like Google Authenticator or Microsoft Authenticator to generate 6-digit verification codes.", mfaRequirementMessage: authenticatorMfaRequirementMessage, mfaRequirementMessageIsWarning: !!(mfaRequirement === null || mfaRequirement === void 0 ? void 0 : mfaRequirement.authenticatorApp) &&
38
+ !!authenticatorMfaRequirementMessage, cypressPrefix: "mfa-authenticator", onSetup: () => beginMfaSetup('authenticator'), onDisable: () => beginDisablingMfaMethod('authenticator'), onSetPreferred: () => setPreferredMfaMethod('authenticator') }), _jsx(Divider, { sx: { my: 2 } }), _jsx(MfaMethodRow, { isEnabled: mfaSettings.sms.enabled, isPreferred: mfaSettings.sms.preferred, isUsedForLogin: isMfaMethodUsedForLogin(mfaSettings, 'sms'), isSettingUp: isSettingUpMfa && settingUpMfaMethod === 'sms', isSettingPreferredMfaMethod: isSettingPreferredMfaMethod, isSetupDisabled: !!loadingError || isSettingUpMfa, showSetupErrorTooltip: !!loadingError, title: "SMS", description: "Receive a one-time verification code via SMS each time MFA is required.", detail: phoneDetail, mfaRequirementMessage: smsMfaRequirementMessage, mfaRequirementMessageIsWarning: !!(mfaRequirement === null || mfaRequirement === void 0 ? void 0 : mfaRequirement.sms) && !!smsMfaRequirementMessage, cypressPrefix: "mfa-sms", onSetup: () => beginMfaSetup('sms'), onDisable: () => beginDisablingMfaMethod('sms'), onSetPreferred: () => setPreferredMfaMethod('sms'), extraButtons: !!mfaSettings.sms.phoneNumber && !mfaSettings.sms.enabled ? (_jsx(Button, { size: "small", variant: "outlined", disabled: !!loadingError, onClick: beginRemovingPhoneNumber, "data-cypress": "mfa-sms-remove-phone-button", children: "Remove Phone" })) : undefined })] }));
31
39
  }
32
- function MfaSetup({ ssoSetupUrl }) {
40
+ function MfaSetup({ ssoSetupUrl, mfaRequirement, accessScopeLabel }) {
33
41
  const { isExternalIdentityProviderUser } = useMfa();
34
42
  if (ssoSetupUrl) {
35
43
  return (_jsx(Button, { variant: "outlined", size: "small", component: "a", href: ssoSetupUrl, target: "_blank", rel: "noopener noreferrer", "data-cypress": "configure-mfa-button", children: "Configure MFA" }));
@@ -37,7 +45,7 @@ function MfaSetup({ ssoSetupUrl }) {
37
45
  if (isExternalIdentityProviderUser) {
38
46
  return (_jsx(Tooltip, { title: "MFA must be configured in your login provider.", children: _jsx("span", { children: _jsx(Button, { variant: "outlined", disabled: isExternalIdentityProviderUser, "data-cypress": "configure-mfa-button", children: "Configure MFA" }) }) }));
39
47
  }
40
- return (_jsxs(_Fragment, { children: [_jsx(MfaMethodList, {}), _jsx(MfaDisableDialog, {}), _jsx(MfaRemovePhoneNumberDialog, {}), _jsx(MfaPhoneNumberDialog, {}), _jsx(MfaAuthenticatorAppDialog, {}), _jsx(MfaErrorSnackbar, {}), _jsx(MfaSuccessSnackbar, {})] }));
48
+ return (_jsxs(_Fragment, { children: [_jsx(MfaMethodList, { mfaRequirement: mfaRequirement, accessScopeLabel: accessScopeLabel }), _jsx(MfaDisableDialog, {}), _jsx(MfaRemovePhoneNumberDialog, {}), _jsx(MfaPhoneNumberDialog, {}), _jsx(MfaAuthenticatorAppDialog, {}), _jsx(MfaErrorSnackbar, {}), _jsx(MfaSuccessSnackbar, {})] }));
41
49
  }
42
50
  /**
43
51
  * React Component that provides a mechanism for app users to configure Multi
@@ -54,28 +62,27 @@ function MfaSetup({ ssoSetupUrl }) {
54
62
  * } from '@oneblink/apps-react'
55
63
  *
56
64
  * function Component() {
57
- * return <MultiFactorAuthentication />
65
+ * return <MultiFactorAuthentication mfaRequirement={undefined} />
58
66
  * }
59
67
  *
60
- * function App() {
68
+ * function AppWithMfaRequirement({ mfaRequirement }) {
61
69
  * return (
62
70
  * <MfaProvider>
63
- * <Component />
71
+ * <MultiFactorAuthentication mfaRequirement={mfaRequirement} />
64
72
  * </MfaProvider>
65
73
  * )
66
74
  * }
67
- *
68
- * const root = document.getElementById('root')
69
- * if (root) {
70
- * ReactDOM.render(<App />, root)
71
- * }
72
75
  * ```
73
76
  *
74
77
  * @param props
78
+ * @param props.mfaRequirement - The MFA methods allowed by your administrator
79
+ * for using this app. Pass `undefined` when the app has no MFA requirement.
80
+ * Users can still enable other methods, but will be warned when their
81
+ * configuration does not meet this requirement.
75
82
  * @returns
76
83
  * @group Components
77
84
  */
78
- export default function MultiFactorAuthentication({ ssoSetupUrl }) {
79
- return (_jsx(Grid, { size: { xs: 'grow', lg: 8 }, children: _jsx(Box, { padding: 3, children: _jsx(Paper, { children: _jsx(Box, { padding: 3, children: _jsx(Grid, { container: true, spacing: 2, alignItems: "center", children: _jsxs(Grid, { size: { xs: 'grow' }, children: [_jsxs(Typography, { variant: "h4", fontWeight: "light", children: ["Multi Factor Authentication ", _jsx(MfaStatusChip, {})] }), _jsx(Box, { marginY: 1, children: _jsx(Divider, {}) }), _jsx(Typography, { variant: "body2", paragraph: true, children: "Multi factor authentication (MFA), also known as two factor authentication (2FA), is a best practice that requires a second authentication factor in addition to user name and password sign-in credentials. We strongly recommend enabling MFA to enhance your account security." }), _jsx(MfaSetup, { ssoSetupUrl: ssoSetupUrl })] }) }) }) }) }) }));
85
+ export default function MultiFactorAuthentication({ mfaRequirement, ssoSetupUrl, accessScopeLabel = 'App', }) {
86
+ return (_jsx(Grid, { size: { xs: 'grow', lg: 8 }, children: _jsx(Box, { padding: 3, children: _jsx(Paper, { children: _jsx(Box, { padding: 3, children: _jsx(Grid, { container: true, spacing: 2, alignItems: "center", children: _jsxs(Grid, { size: { xs: 'grow' }, children: [_jsxs(Typography, { variant: "h4", fontWeight: "light", children: ["Multi Factor Authentication", ' ', _jsx(MfaStatusChip, {})] }), _jsx(Box, { marginY: 1, children: _jsx(Divider, {}) }), _jsx(Typography, { variant: "body2", paragraph: true, children: "Multi factor authentication (MFA), also known as two factor authentication (2FA), is a best practice that requires a second authentication factor in addition to user name and password sign-in credentials. We strongly recommend enabling MFA to enhance your account security." }), _jsx(MfaSetup, { ssoSetupUrl: ssoSetupUrl, mfaRequirement: mfaRequirement, accessScopeLabel: accessScopeLabel })] }) }) }) }) }) }));
80
87
  }
81
88
  //# sourceMappingURL=MultiFactorAuthentication.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MultiFactorAuthentication.js","sourceRoot":"","sources":["../../../src/components/mfa/MultiFactorAuthentication.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo } from 'react'\nimport {\n Box,\n Button,\n Divider,\n Grid,\n Paper,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport MfaAuthenticatorAppDialog from './MfaAuthenticatorAppDialog'\nimport useMfa from '../../hooks/useMfa'\nimport MfaDisableDialog from './MfaDisableDialog'\nimport MfaRemovePhoneNumberDialog from './MfaRemovePhoneNumberDialog'\nimport MfaPhoneNumberDialog from './MfaPhoneNumberDialog'\nimport MfaMethodRow from './MfaMethodRow'\nimport MfaSuccessSnackbar from './MfaSuccessSnackbar'\nimport MfaErrorSnackbar from './MfaErrorSnackbar'\nimport MfaStatusChip from './MfaStatusChip'\n\ntype Props = {\n ssoSetupUrl: string | undefined\n}\n\nfunction MfaMethodList() {\n const {\n mfaSettings,\n loadingError,\n isLoading,\n isSettingUpMfa,\n settingUpMfaMethod,\n isSettingPreferredMfaMethod,\n beginMfaSetup,\n beginDisablingMfaMethod,\n setPreferredMfaMethod,\n beginRemovingPhoneNumber,\n } = useMfa()\n\n const phoneDetail = useMemo(() => {\n if (!mfaSettings.sms.phoneNumber) {\n return undefined\n }\n\n if (!mfaSettings.sms.isPhoneNumberVerified) {\n return `Phone number: ${mfaSettings.sms.phoneNumber} (not verified)`\n }\n\n return `Phone number: ${mfaSettings.sms.phoneNumber}`\n }, [mfaSettings])\n\n if (isLoading) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n Loading MFA methods...\n </Typography>\n )\n }\n\n if (loadingError) {\n return (\n <Typography variant=\"body2\" color=\"error\">\n We are unable to load your MFA methods. Please try again by clicking the\n reload button on the chip above.\n </Typography>\n )\n }\n\n return (\n <>\n <MfaMethodRow\n isEnabled={mfaSettings.authenticator.enabled}\n isPreferred={mfaSettings.authenticator.preferred}\n isSettingUp={\n isSettingUpMfa && settingUpMfaMethod === 'authenticator'\n }\n isSettingPreferredMfaMethod={isSettingPreferredMfaMethod}\n isSetupDisabled={!!loadingError || isSettingUpMfa}\n showSetupErrorTooltip={!!loadingError}\n title=\"Authenticator App\"\n description=\"Use an app like Google Authenticator or Microsoft Authenticator to generate 6-digit verification codes.\"\n cypressPrefix=\"mfa-authenticator\"\n onSetup={() => beginMfaSetup('authenticator')}\n onDisable={() => beginDisablingMfaMethod('authenticator')}\n onSetPreferred={() => setPreferredMfaMethod('authenticator')}\n />\n <Divider sx={{ my: 2 }} />\n <MfaMethodRow\n isEnabled={mfaSettings.sms.enabled}\n isPreferred={mfaSettings.sms.preferred}\n isSettingUp={isSettingUpMfa && settingUpMfaMethod === 'sms'}\n isSettingPreferredMfaMethod={isSettingPreferredMfaMethod}\n isSetupDisabled={!!loadingError || isSettingUpMfa}\n showSetupErrorTooltip={!!loadingError}\n title=\"SMS\"\n description=\"Receive a one-time verification code via SMS each time MFA is required.\"\n detail={phoneDetail}\n cypressPrefix=\"mfa-sms\"\n onSetup={() => beginMfaSetup('sms')}\n onDisable={() => beginDisablingMfaMethod('sms')}\n onSetPreferred={() => setPreferredMfaMethod('sms')}\n extraButtons={\n !!mfaSettings.sms.phoneNumber && !mfaSettings.sms.enabled ? (\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={!!loadingError}\n onClick={beginRemovingPhoneNumber}\n data-cypress=\"mfa-sms-remove-phone-button\"\n >\n Remove Phone\n </Button>\n ) : undefined\n }\n />\n </>\n )\n}\n\nfunction MfaSetup({ ssoSetupUrl }: Props) {\n const { isExternalIdentityProviderUser } = useMfa()\n\n if (ssoSetupUrl) {\n return (\n <Button\n variant=\"outlined\"\n size=\"small\"\n component=\"a\"\n href={ssoSetupUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n data-cypress=\"configure-mfa-button\"\n >\n Configure MFA\n </Button>\n )\n }\n\n if (isExternalIdentityProviderUser) {\n return (\n <Tooltip title=\"MFA must be configured in your login provider.\">\n <span>\n <Button\n variant=\"outlined\"\n disabled={isExternalIdentityProviderUser}\n data-cypress=\"configure-mfa-button\"\n >\n Configure MFA\n </Button>\n </span>\n </Tooltip>\n )\n }\n\n return (\n <>\n <MfaMethodList />\n <MfaDisableDialog />\n <MfaRemovePhoneNumberDialog />\n <MfaPhoneNumberDialog />\n <MfaAuthenticatorAppDialog />\n <MfaErrorSnackbar />\n <MfaSuccessSnackbar />\n </>\n )\n}\n\n/**\n * React Component that provides a mechanism for app users to configure Multi\n * Factor Authentication. `<MfaProvider />` must be provided above this\n * component in the component tree.\n *\n * #### Example\n *\n * ```js\n * import * as React from 'react'\n * import {\n * MfaProvider,\n * MultiFactorAuthentication,\n * } from '@oneblink/apps-react'\n *\n * function Component() {\n * return <MultiFactorAuthentication />\n * }\n *\n * function App() {\n * return (\n * <MfaProvider>\n * <Component />\n * </MfaProvider>\n * )\n * }\n *\n * const root = document.getElementById('root')\n * if (root) {\n * ReactDOM.render(<App />, root)\n * }\n * ```\n *\n * @param props\n * @returns\n * @group Components\n */\nexport default function MultiFactorAuthentication({ ssoSetupUrl }: Props) {\n return (\n <Grid size={{ xs: 'grow', lg: 8 }}>\n <Box padding={3}>\n <Paper>\n <Box padding={3}>\n <Grid container spacing={2} alignItems=\"center\">\n <Grid size={{ xs: 'grow' }}>\n <Typography variant=\"h4\" fontWeight=\"light\">\n Multi Factor Authentication <MfaStatusChip />\n </Typography>\n <Box marginY={1}>\n <Divider />\n </Box>\n <Typography variant=\"body2\" paragraph>\n Multi factor authentication (MFA), also known as two factor\n authentication (2FA), is a best practice that requires a\n second authentication factor in addition to user name and\n password sign-in credentials. We strongly recommend enabling\n MFA to enhance your account security.\n </Typography>\n <MfaSetup ssoSetupUrl={ssoSetupUrl} />\n </Grid>\n </Grid>\n </Box>\n </Paper>\n </Box>\n </Grid>\n )\n}\n"]}
1
+ {"version":3,"file":"MultiFactorAuthentication.js","sourceRoot":"","sources":["../../../src/components/mfa/MultiFactorAuthentication.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo } from 'react'\nimport {\n Box,\n Button,\n Divider,\n Grid,\n Paper,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { MiscTypes } from '@oneblink/types'\nimport MfaAuthenticatorAppDialog from './MfaAuthenticatorAppDialog'\nimport useMfa from '../../hooks/useMfa'\nimport MfaDisableDialog from './MfaDisableDialog'\nimport MfaRemovePhoneNumberDialog from './MfaRemovePhoneNumberDialog'\nimport MfaPhoneNumberDialog from './MfaPhoneNumberDialog'\nimport MfaMethodRow from './MfaMethodRow'\nimport MfaSuccessSnackbar from './MfaSuccessSnackbar'\nimport MfaErrorSnackbar from './MfaErrorSnackbar'\nimport MfaStatusChip from './MfaStatusChip'\nimport {\n formatMfaMethodRequirementMessage,\n isMfaMethodUsedForLogin,\n} from '../../utils/mfa-requirement'\n\ntype Props = {\n mfaRequirement: MiscTypes.MfaRequirement | undefined\n ssoSetupUrl?: string\n accessScopeLabel?: string\n}\n\nfunction MfaMethodList({\n mfaRequirement,\n accessScopeLabel = 'app',\n}: Pick<Props, 'mfaRequirement' | 'accessScopeLabel'>) {\n const {\n mfaSettings,\n loadingError,\n isLoading,\n isSettingUpMfa,\n settingUpMfaMethod,\n isSettingPreferredMfaMethod,\n beginMfaSetup,\n beginDisablingMfaMethod,\n setPreferredMfaMethod,\n beginRemovingPhoneNumber,\n } = useMfa()\n\n const phoneDetail = useMemo(() => {\n if (!mfaSettings.sms.phoneNumber) {\n return undefined\n }\n\n if (!mfaSettings.sms.isPhoneNumberVerified) {\n return `Phone number: ${mfaSettings.sms.phoneNumber} (not verified)`\n }\n\n return `Phone number: ${mfaSettings.sms.phoneNumber}`\n }, [mfaSettings])\n\n const authenticatorMfaRequirementMessage = useMemo(() => {\n return formatMfaMethodRequirementMessage(\n 'authenticatorApp',\n mfaRequirement,\n mfaSettings,\n accessScopeLabel,\n )\n }, [accessScopeLabel, mfaRequirement, mfaSettings])\n\n const smsMfaRequirementMessage = useMemo(() => {\n return formatMfaMethodRequirementMessage(\n 'sms',\n mfaRequirement,\n mfaSettings,\n accessScopeLabel,\n )\n }, [accessScopeLabel, mfaRequirement, mfaSettings])\n\n if (isLoading) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n Loading MFA methods...\n </Typography>\n )\n }\n\n if (loadingError) {\n return (\n <Typography variant=\"body2\" color=\"error\">\n We are unable to load your MFA methods. Please try again by clicking the\n reload button on the chip above.\n </Typography>\n )\n }\n\n return (\n <>\n <MfaMethodRow\n isEnabled={mfaSettings.authenticator.enabled}\n isPreferred={mfaSettings.authenticator.preferred}\n isUsedForLogin={isMfaMethodUsedForLogin(\n mfaSettings,\n 'authenticatorApp',\n )}\n isSettingUp={isSettingUpMfa && settingUpMfaMethod === 'authenticator'}\n isSettingPreferredMfaMethod={isSettingPreferredMfaMethod}\n isSetupDisabled={!!loadingError || isSettingUpMfa}\n showSetupErrorTooltip={!!loadingError}\n title=\"Authenticator App\"\n description=\"Use an app like Google Authenticator or Microsoft Authenticator to generate 6-digit verification codes.\"\n mfaRequirementMessage={authenticatorMfaRequirementMessage}\n mfaRequirementMessageIsWarning={\n !!mfaRequirement?.authenticatorApp &&\n !!authenticatorMfaRequirementMessage\n }\n cypressPrefix=\"mfa-authenticator\"\n onSetup={() => beginMfaSetup('authenticator')}\n onDisable={() => beginDisablingMfaMethod('authenticator')}\n onSetPreferred={() => setPreferredMfaMethod('authenticator')}\n />\n <Divider sx={{ my: 2 }} />\n <MfaMethodRow\n isEnabled={mfaSettings.sms.enabled}\n isPreferred={mfaSettings.sms.preferred}\n isUsedForLogin={isMfaMethodUsedForLogin(mfaSettings, 'sms')}\n isSettingUp={isSettingUpMfa && settingUpMfaMethod === 'sms'}\n isSettingPreferredMfaMethod={isSettingPreferredMfaMethod}\n isSetupDisabled={!!loadingError || isSettingUpMfa}\n showSetupErrorTooltip={!!loadingError}\n title=\"SMS\"\n description=\"Receive a one-time verification code via SMS each time MFA is required.\"\n detail={phoneDetail}\n mfaRequirementMessage={smsMfaRequirementMessage}\n mfaRequirementMessageIsWarning={\n !!mfaRequirement?.sms && !!smsMfaRequirementMessage\n }\n cypressPrefix=\"mfa-sms\"\n onSetup={() => beginMfaSetup('sms')}\n onDisable={() => beginDisablingMfaMethod('sms')}\n onSetPreferred={() => setPreferredMfaMethod('sms')}\n extraButtons={\n !!mfaSettings.sms.phoneNumber && !mfaSettings.sms.enabled ? (\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={!!loadingError}\n onClick={beginRemovingPhoneNumber}\n data-cypress=\"mfa-sms-remove-phone-button\"\n >\n Remove Phone\n </Button>\n ) : undefined\n }\n />\n </>\n )\n}\n\nfunction MfaSetup({ ssoSetupUrl, mfaRequirement, accessScopeLabel }: Props) {\n const { isExternalIdentityProviderUser } = useMfa()\n\n if (ssoSetupUrl) {\n return (\n <Button\n variant=\"outlined\"\n size=\"small\"\n component=\"a\"\n href={ssoSetupUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n data-cypress=\"configure-mfa-button\"\n >\n Configure MFA\n </Button>\n )\n }\n\n if (isExternalIdentityProviderUser) {\n return (\n <Tooltip title=\"MFA must be configured in your login provider.\">\n <span>\n <Button\n variant=\"outlined\"\n disabled={isExternalIdentityProviderUser}\n data-cypress=\"configure-mfa-button\"\n >\n Configure MFA\n </Button>\n </span>\n </Tooltip>\n )\n }\n\n return (\n <>\n <MfaMethodList\n mfaRequirement={mfaRequirement}\n accessScopeLabel={accessScopeLabel}\n />\n <MfaDisableDialog />\n <MfaRemovePhoneNumberDialog />\n <MfaPhoneNumberDialog />\n <MfaAuthenticatorAppDialog />\n <MfaErrorSnackbar />\n <MfaSuccessSnackbar />\n </>\n )\n}\n\n/**\n * React Component that provides a mechanism for app users to configure Multi\n * Factor Authentication. `<MfaProvider />` must be provided above this\n * component in the component tree.\n *\n * #### Example\n *\n * ```js\n * import * as React from 'react'\n * import {\n * MfaProvider,\n * MultiFactorAuthentication,\n * } from '@oneblink/apps-react'\n *\n * function Component() {\n * return <MultiFactorAuthentication mfaRequirement={undefined} />\n * }\n *\n * function AppWithMfaRequirement({ mfaRequirement }) {\n * return (\n * <MfaProvider>\n * <MultiFactorAuthentication mfaRequirement={mfaRequirement} />\n * </MfaProvider>\n * )\n * }\n * ```\n *\n * @param props\n * @param props.mfaRequirement - The MFA methods allowed by your administrator\n * for using this app. Pass `undefined` when the app has no MFA requirement.\n * Users can still enable other methods, but will be warned when their\n * configuration does not meet this requirement.\n * @returns\n * @group Components\n */\nexport default function MultiFactorAuthentication({\n mfaRequirement,\n ssoSetupUrl,\n accessScopeLabel = 'App',\n}: Props) {\n return (\n <Grid size={{ xs: 'grow', lg: 8 }}>\n <Box padding={3}>\n <Paper>\n <Box padding={3}>\n <Grid container spacing={2} alignItems=\"center\">\n <Grid size={{ xs: 'grow' }}>\n <Typography variant=\"h4\" fontWeight=\"light\">\n Multi Factor Authentication{' '}\n <MfaStatusChip />\n </Typography>\n <Box marginY={1}>\n <Divider />\n </Box>\n <Typography variant=\"body2\" paragraph>\n Multi factor authentication (MFA), also known as two factor\n authentication (2FA), is a best practice that requires a\n second authentication factor in addition to user name and\n password sign-in credentials. We strongly recommend enabling\n MFA to enhance your account security.\n </Typography>\n <MfaSetup\n ssoSetupUrl={ssoSetupUrl}\n mfaRequirement={mfaRequirement}\n accessScopeLabel={accessScopeLabel}\n />\n </Grid>\n </Grid>\n </Box>\n </Paper>\n </Box>\n </Grid>\n )\n}\n"]}
@@ -83,7 +83,7 @@ function BarcodeScannerCameraLoader(props) {
83
83
  }
84
84
  return _jsx(BarcodeScanner, { ...props, cameraDevices: state.result });
85
85
  }
86
- function BarcodeScanner({ id, restrictedBarcodeTypes, onScan, onClose, ...props }) {
86
+ function BarcodeScanner({ id, restrictedBarcodeTypes, onScan, onClose, cameraDevices, ...props }) {
87
87
  const formatsToSupport = React.useMemo(() => {
88
88
  return restrictedBarcodeTypes === null || restrictedBarcodeTypes === void 0 ? void 0 : restrictedBarcodeTypes.reduce((memo, barcodeType) => {
89
89
  const format = Html5QrcodeSupportedFormats[barcodeType];
@@ -93,36 +93,84 @@ function BarcodeScanner({ id, restrictedBarcodeTypes, onScan, onClose, ...props
93
93
  return memo;
94
94
  }, []);
95
95
  }, [restrictedBarcodeTypes]);
96
+ const [scannerState, setScannerState] = React.useState({ state: 'loading' });
96
97
  React.useEffect(() => {
98
+ setScannerState({ state: 'loading' });
97
99
  const html5Qrcode = new Html5Qrcode(id, {
98
100
  verbose: !!localStorage.getItem('BARCODE_SCANNER_VERBOSE'),
99
101
  formatsToSupport: (formatsToSupport === null || formatsToSupport === void 0 ? void 0 : formatsToSupport.length) ? formatsToSupport : undefined,
100
102
  });
101
- html5Qrcode
102
- .start({
103
- facingMode: 'environment',
104
- }, {
103
+ const backFacingCamera = cameraDevices.find((device) => /back|rear|environment/i.test(device.label));
104
+ const preferredCamera = backFacingCamera !== null && backFacingCamera !== void 0 ? backFacingCamera : cameraDevices[0];
105
+ const scanConfig = {
105
106
  fps: 20,
106
- qrbox: {
107
- width: 400,
108
- height: 400,
107
+ aspectRatio: 1,
108
+ qrbox: (viewfinderWidth, viewfinderHeight) => {
109
+ const size = Math.min(250, Math.floor(Math.min(viewfinderWidth, viewfinderHeight) * 0.75));
110
+ return { width: size, height: size };
109
111
  },
110
- }, function onScanSuccess(decodedText, decodedResult) {
112
+ };
113
+ const onScanSuccess = (decodedText, decodedResult) => {
111
114
  console.log('Barcode scanner decoded result:', decodedResult);
112
115
  onScan(decodedText);
113
- }, function onScanFailure() {
116
+ };
117
+ const onScanFailure = () => {
114
118
  // do nothing and keep scanning
115
- })
116
- .catch((error) => {
117
- console.warn('Failed to start scanning', error);
118
- });
119
+ };
120
+ const abortController = new AbortController();
121
+ const begin = async () => {
122
+ try {
123
+ // Attempt to start with the back facing camera
124
+ await html5Qrcode.start({ deviceId: preferredCamera.id }, scanConfig, onScanSuccess, onScanFailure);
125
+ if (abortController.signal.aborted) {
126
+ return;
127
+ }
128
+ setScannerState({ state: 'ready' });
129
+ }
130
+ catch (deviceIdError) {
131
+ if (abortController.signal.aborted) {
132
+ return;
133
+ }
134
+ console.warn('Failed to start scanning with deviceId', deviceIdError);
135
+ try {
136
+ // If the back facing camera is not available, try to start in environment mode
137
+ await html5Qrcode.start({ facingMode: 'environment' }, scanConfig, onScanSuccess, onScanFailure);
138
+ if (abortController.signal.aborted) {
139
+ return;
140
+ }
141
+ setScannerState({ state: 'ready' });
142
+ }
143
+ catch (facingModeError) {
144
+ if (abortController.signal.aborted) {
145
+ return;
146
+ }
147
+ console.warn('Failed to start scanning', facingModeError);
148
+ setScannerState({
149
+ state: 'error',
150
+ error: facingModeError instanceof Error
151
+ ? facingModeError
152
+ : new Error(String(facingModeError)),
153
+ });
154
+ }
155
+ }
156
+ };
157
+ begin();
119
158
  return () => {
120
- html5Qrcode.stop().catch((error) => {
159
+ abortController.abort();
160
+ if (!html5Qrcode.isScanning) {
161
+ return;
162
+ }
163
+ try {
164
+ html5Qrcode.stop().catch((error) => {
165
+ console.warn('Failed to stop barcode scanner', error);
166
+ });
167
+ }
168
+ catch (error) {
121
169
  console.warn('Failed to stop barcode scanner', error);
122
- });
170
+ }
123
171
  };
124
- }, [formatsToSupport, id, onScan]);
125
- return (_jsxs(BarcodeScannerFigure, { onClose: onClose, children: [_jsx("div", { className: "figure-content-absolute-center", children: _jsx(OnLoading, { small: true }) }), _jsx("div", { id: id, className: "ob-figure__barcode-scanner", "aria-describedby": props['aria-describedby'] })] }));
172
+ }, [cameraDevices, formatsToSupport, id, onScan]);
173
+ return (_jsxs(BarcodeScannerFigure, { onClose: onClose, children: [scannerState.state === 'loading' && (_jsx("div", { className: "figure-content-absolute-center", children: _jsx(OnLoading, { small: true }) })), scannerState.state === 'error' && (_jsx(BarcodeScannerError, { title: "Whoops...", message: scannerState.error.message })), _jsx("div", { id: id, className: "ob-figure__barcode-scanner", "aria-describedby": props['aria-describedby'] })] }));
126
174
  }
127
175
  function BarcodeScannerFigure({ onClose, children, }) {
128
176
  return (_jsxs("div", { children: [_jsx("figure", { className: "ob-figure", children: children }), _jsx("div", { className: "buttons ob-buttons", children: _jsx("button", { type: "button", className: "button ob-button ob-button__cancel is-light cypress-cancel-scan-barcode-button", onClick: onClose, children: "Cancel" }) })] }));