@connectid-tools/idp-selector-react 6.9.0 → 6.10.0-alpha.0

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.
package/CidButton.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import './CidButton.css';
2
2
  import './global.css';
3
- import { CertificationStatus, CidButtonLabel, CidButtonSize, CidButtonTheme, CidButtonType, Claim, IntroTextType, LogoMode, RequiredCertification } from './types';
3
+ import { CertificationStatus, CidButtonLabel, CidButtonSize, CidButtonTheme, CidButtonType, Claim, CompabilityWarnings, IntroTextType, LogoMode, RequiredCertification } from './types';
4
4
  type CidButtonProps = {
5
- onProceed: (authorisationServerId: string) => void;
5
+ onProceed: (authorisationServerId: string, compabilityWarnings: CompabilityWarnings) => void;
6
6
  claims: Claim[];
7
7
  requiredClaims?: Claim[];
8
8
  rpLogoUrl?: string;
@@ -21,6 +21,10 @@ type CidButtonProps = {
21
21
  logoLockupStyle?: LogoMode;
22
22
  showInfoContent?: boolean;
23
23
  onError?: (error: string) => void;
24
+ forceMobile?: boolean;
25
+ forceIos?: boolean;
26
+ forceAndroid?: boolean;
27
+ forceIncognito?: boolean;
24
28
  };
25
29
  export declare const CidButton: React.FC<CidButtonProps>;
26
30
  export {};
package/CidButton.js CHANGED
@@ -27,7 +27,14 @@ import './global.css';
27
27
  import { getCurrentUrlOrigin } from './navigator';
28
28
  import { isIntroTextTypeValid, isRequiredClaimsValid } from './validator';
29
29
  import detectIncognito from './incognito';
30
- export const CidButton = ({ onProceed, claims, requiredClaims, rpLogoUrl, requiredCertifications, label = 'Verify', size = 'regular', type, introTextType, fullProcessCollapsible = false, participantsUri = 'https://data.directory.connectid.com.au/participants', certificationStatus = 'Active', rpDataSharingDescription = 'Your details will be shared with the current website.', enableManualVerification = false, showAllParticipants = false, theme = 'dark', logoLockupStyle = 'light', showInfoContent = false, onError = () => { }, }) => {
30
+ import { getDeviceType, isAndroid, isChromeBrowser, isIos, isSamsungBrowser } from './userAgent';
31
+ import { hasCertification } from './certification';
32
+ import { getCompability } from './compability';
33
+ export const CidButton = ({ onProceed, claims, requiredClaims, rpLogoUrl, requiredCertifications, label = 'Verify', size = 'regular', type, introTextType, fullProcessCollapsible = false, participantsUri = 'https://data.directory.connectid.com.au/participants', certificationStatus = 'Active', rpDataSharingDescription = 'Your details will be shared with the current website.', enableManualVerification = false, showAllParticipants = false, theme = 'dark', logoLockupStyle = 'light', showInfoContent = false, onError = () => { }, forceMobile = false, // only for testing purposes
34
+ forceIos = false, // only for testing purposes
35
+ forceAndroid = false, // only for testing purposes
36
+ forceIncognito = false, // only for testing purposes
37
+ }) => {
31
38
  const { data: participants, error, isLoading, } = useSWR(participantsUri, () => __awaiter(void 0, void 0, void 0, function* () {
32
39
  const response = yield fetch(participantsUri);
33
40
  if (!response.ok)
@@ -112,10 +119,21 @@ export const CidButton = ({ onProceed, claims, requiredClaims, rpLogoUrl, requir
112
119
  });
113
120
  track();
114
121
  }, []);
115
- const handleOnProceed = (authorisationServerId, authorisationServerName) => {
122
+ const handleOnProceed = (authorisationServerId, authorisationServerName, authorisationServerCertifications) => {
116
123
  analytics === null || analytics === void 0 ? void 0 : analytics.track('IDP Selected', Object.assign(Object.assign({}, analyticsData), { authorisationServerId, idp_name: authorisationServerName, incognito: isIncognito }));
117
124
  setShowPopup(false);
118
- onProceed(authorisationServerId);
125
+ const isMobile = forceMobile || getDeviceType() === 'mobile';
126
+ const idpHasAppCertification = hasCertification(authorisationServerCertifications, 'Channel', 'app');
127
+ const compability = getCompability({
128
+ idpHasAppCertification,
129
+ isMobile,
130
+ isIncognito: forceIncognito || !!isIncognito,
131
+ isAndroid: forceAndroid || isAndroid(),
132
+ isIos: forceIos || isIos(),
133
+ isSamsungBrowser: isSamsungBrowser(),
134
+ isChromeBrowser: isChromeBrowser(),
135
+ });
136
+ onProceed(authorisationServerId, compability);
119
137
  };
120
138
  const handleButtonClick = () => {
121
139
  setShowPopup(true);
package/Popup.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import './global.css';
2
2
  import './Popup.css';
3
- import { CertificationStatus, Claim, Participant, RequiredCertification } from './types';
3
+ import { AuthorisationServerCertification, CertificationStatus, Claim, Participant, RequiredCertification } from './types';
4
4
  type PopupProps = {
5
- onProceed: (authorisationServerId: string, authorisationServerName: string) => void;
5
+ onProceed: (authorisationServerId: string, authorisationServerName: string, authorisationServerCertifications: AuthorisationServerCertification[]) => void;
6
6
  onCancel: () => void;
7
7
  onError?: (error: string) => void;
8
8
  claims: Claim[];
package/Popup.js CHANGED
@@ -145,7 +145,7 @@ export const Popup = ({ onProceed, onCancel, onError, claims, requiredClaims, sh
145
145
  });
146
146
  const missingAuthorisationServerWithProfileRedirectFapi2 = authorisationServerWithProfileRedirectFapi2.length !== filteredAuthorisationServers.length;
147
147
  const hasManualIdp = enableManualVerification && !!manualAuthorisationServer;
148
- return (_jsxs(_Fragment, { children: [filteredAuthorisationServers.length > 0 ? (_jsxs("div", { className: "cid-idp-selector-popup-provider-wrapper", children: [_jsx("h3", { className: "cid-idp-selector-popup-subtitle", children: "Choose a provider" }), _jsx("ul", { className: "cid-idp-selector-popup-participants", "data-testid": "participants", children: filteredAuthorisationServers.map(({ AuthorisationServerId, CustomerFriendlyName, CustomerFriendlyLogoUri }) => (_jsxs("li", { "data-testid": "participant", className: "cid-idp-selector-popup-participant", tabIndex: 0, onClick: () => onProceed(AuthorisationServerId, CustomerFriendlyName), children: [_jsx("img", { className: "cid-idp-selector-popup-participant-icon", src: CustomerFriendlyLogoUri, alt: `${CustomerFriendlyName} logo` }), _jsx("div", { className: "cid-idp-selector-popup-participant-name", title: CustomerFriendlyName, "data-testid": "participant-name", children: CustomerFriendlyName })] }, AuthorisationServerId))) }), missingAuthorisationServerWithProfileRedirectFapi2 && (_jsxs("div", { className: "cid-idp-selector-popup-missing", "data-testid": "missing-auth-server", children: [_jsx("p", { className: "cid-idp-selector-popup-missing-title", children: "Can't find your provider?" }), _jsx("p", { className: "cid-idp-selector-popup-missing-text", children: "Not all of our identity providers are available for this verification." })] }))] })) : (_jsx("div", { className: "cid-idp-selector-popup-info-box-error-wrapper", "data-testid": "popup-error-info-box", children: _jsx(NoIdpError, { hasManualIdp: hasManualIdp }) })), enableManualVerification && manualAuthorisationServer && (_jsx(_Fragment, { children: _jsxs("p", { className: "cid-idp-selector-popup-manual", children: [_jsx("span", { className: "cid-idp-selector-popup-manual-title", "data-testid": "manual-verification-text", children: "Or, use a document to verify your identity" }), _jsx("span", { className: "cid-idp-selector-popup-manual-action", tabIndex: 0, onClick: () => onProceed(manualAuthorisationServer.AuthorisationServerId, manualAuthorisationServer.CustomerFriendlyName), "data-testid": "manual-verification-action", role: "link", children: "Verify with a document" }), _jsx("span", { className: "cid-idp-selector-popup-manual-description", children: "ConnectID does not collect, hold or store your personal information at any time." })] }) }))] }));
148
+ return (_jsxs(_Fragment, { children: [filteredAuthorisationServers.length > 0 ? (_jsxs("div", { className: "cid-idp-selector-popup-provider-wrapper", children: [_jsx("h3", { className: "cid-idp-selector-popup-subtitle", children: "Choose a provider" }), _jsx("ul", { className: "cid-idp-selector-popup-participants", "data-testid": "participants", children: filteredAuthorisationServers.map(({ AuthorisationServerId, CustomerFriendlyName, CustomerFriendlyLogoUri, AuthorisationServerCertifications }) => (_jsxs("li", { "data-testid": "participant", className: "cid-idp-selector-popup-participant", tabIndex: 0, onClick: () => onProceed(AuthorisationServerId, CustomerFriendlyName, AuthorisationServerCertifications), children: [_jsx("img", { className: "cid-idp-selector-popup-participant-icon", src: CustomerFriendlyLogoUri, alt: `${CustomerFriendlyName} logo` }), _jsx("div", { className: "cid-idp-selector-popup-participant-name", title: CustomerFriendlyName, "data-testid": "participant-name", children: CustomerFriendlyName })] }, AuthorisationServerId))) }), missingAuthorisationServerWithProfileRedirectFapi2 && (_jsxs("div", { className: "cid-idp-selector-popup-missing", "data-testid": "missing-auth-server", children: [_jsx("p", { className: "cid-idp-selector-popup-missing-title", children: "Can't find your provider?" }), _jsx("p", { className: "cid-idp-selector-popup-missing-text", children: "Not all of our identity providers are available for this verification." })] }))] })) : (_jsx("div", { className: "cid-idp-selector-popup-info-box-error-wrapper", "data-testid": "popup-error-info-box", children: _jsx(NoIdpError, { hasManualIdp: hasManualIdp }) })), enableManualVerification && manualAuthorisationServer && (_jsx(_Fragment, { children: _jsxs("p", { className: "cid-idp-selector-popup-manual", children: [_jsx("span", { className: "cid-idp-selector-popup-manual-title", "data-testid": "manual-verification-text", children: "Or, use a document to verify your identity" }), _jsx("span", { className: "cid-idp-selector-popup-manual-action", tabIndex: 0, onClick: () => onProceed(manualAuthorisationServer.AuthorisationServerId, manualAuthorisationServer.CustomerFriendlyName, manualAuthorisationServer.AuthorisationServerCertifications), "data-testid": "manual-verification-action", role: "link", children: "Verify with a document" }), _jsx("span", { className: "cid-idp-selector-popup-manual-description", children: "ConnectID does not collect, hold or store your personal information at any time." })] }) }))] }));
149
149
  };
150
150
  return ReactDOM.createPortal(_jsxs("div", { className: "cid-idp-selector-popup", tabIndex: 0, ref: popupRef, onKeyDown: handleEnterEsc, children: [_jsxs("div", { className: "cid-idp-selector-popup-logo-container", children: [_jsxs("div", { className: "cid-idp-selector-popup-logos", children: [_jsxs("svg", { role: "img", focusable: "false", className: "cid-idp-selector-popup-logo", viewBox: "0 0 132.37 157.84", "data-testid": "cid-logo", children: [_jsx("title", { children: "ConnectID logo" }), _jsx("path", { d: "M102.22,91.65c0,19.9-16.13,36.03-36.03,36.03s-36.03-16.13-36.03-36.03,16.13-36.03,36.03-36.03c9.95,0,18.96,4.03,25.48,10.55l40.71-40.71c-6.52-6.52-10.55-15.52-10.55-25.46h-30.14c0,13.45,4.02,25.95,10.92,36.39-10.45-6.9-22.96-10.93-36.42-10.93C29.63,25.47,0,55.1,0,91.65s29.63,66.19,66.19,66.19,66.19-29.63,66.19-66.19h-30.16Z" })] }), rpLogoUrl && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "cid-idp-selector-popup-logo-dots", children: [_jsx("div", { className: "cid-idp-selector-popup-logo-dot" }), _jsx("div", { className: "cid-idp-selector-popup-logo-dot" }), _jsx("div", { className: "cid-idp-selector-popup-logo-dot" })] }), _jsx("img", { className: "cid-idp-selector-popup-logo-rp", src: rpLogoUrl, alt: "RP logo", "data-testid": "rp-logo" })] }))] }), _jsxs("button", { "aria-label": "Close popup", "data-testid": "popup-close", className: "cid-idp-selector-popup-close", onClick: onCancel, tabIndex: 0, ref: closeButtonRef, children: [_jsx("span", { className: "cid-idp-selector-popup-close-label", children: "Close" }), _jsx("svg", { clipRule: "evenodd", fillRule: "evenodd", strokeLinejoin: "round", strokeMiterlimit: "2", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "m12 10.93 5.719-5.72c.146-.146.339-.219.531-.219.404 0 .75.324.75.749 0 .193-.073.385-.219.532l-5.72 5.719 5.719 5.719c.147.147.22.339.22.531 0 .427-.349.75-.75.75-.192 0-.385-.073-.531-.219l-5.719-5.719-5.719 5.719c-.146.146-.339.219-.531.219-.401 0-.75-.323-.75-.75 0-.192.073-.384.22-.531l5.719-5.719-5.72-5.719c-.146-.147-.219-.339-.219-.532 0-.425.346-.749.75-.749.192 0 .385.073.531.219z" }) })] })] }), _jsxs("div", { className: "cid-idp-selector-popup-title-description-container", children: [_jsx("h2", { className: "cid-idp-selector-popup-title", children: popupTitle }), _jsx("p", { className: "cid-idp-selector-popup-description", children: rpDataSharingDescription })] }), renderClaims(), renderAuthorisationServers(), _jsx("div", { className: "cid-idp-selector-popup-info-box-wrapper", children: _jsxs(InfoBox, { children: [_jsx("h4", { className: "cid-idp-selector-popup-info-box-title", children: "What is ConnectID?" }), _jsxs("p", { className: "cid-idp-selector-popup-info-box-text", "data-testid": "whats-cid-text", children: ["ConnectID is an Australian-owned digital identity solution which allows you to securely prove who you are. ConnectID does not see or store your personal data. Visit", ' ', _jsx("a", { className: "cid-idp-selector-popup-info-box-link", href: "https://www.connectid.com.au", onClick: handleConnectIdLinkClicked, target: "_blank", tabIndex: 0, "data-testid": "info-box-link", ref: infoboxLinkRef, children: "connectid.com.au" }), ' ', "to find out more."] })] }) })] }), document.querySelector('body'));
151
151
  };
package/README.md CHANGED
@@ -14,7 +14,7 @@ Place the `<CidButton>` component where you want to render the ConnectID button
14
14
  import { CidButton } from '@connectid-tools/idp-selector-react'
15
15
 
16
16
  <CidButton
17
- onProceed={(authorisationServerClicked: string) => {
17
+ onProceed={(authorisationServerClicked: string, compabilityWarnings: CompabilityWarnings) => {
18
18
  /* do something here */
19
19
  }}
20
20
  claims={['email']}
@@ -31,7 +31,7 @@ Your page should render a widget similar to this 👇 And when the button is cli
31
31
 
32
32
  | key | description | type | required | default |
33
33
  | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------------- |
34
- | onProceed | Callback to be called when IDP Selector `Proceed` button is clicked | (authorisationServerId: string) => void | yes | - |
34
+ | onProceed | Callback to be called when IDP Selector `Proceed` button is clicked | (authorisationServerId: string, compabilityWarnings: CompabilityWarnings) => void | yes | - |
35
35
  | claims | List of claims to be displayed to the user and will be requested from IDP to be shared. If `requiredClaims` not supplied then only IDPs that support all listed `claims` will be displayed to the user. | Claim[] | yes | - |
36
36
  | requiredClaims | List of claims to be supported by IDPs. Must be a subset of `claims`. Where an RP can successfully complete their use case without all claims specified in `claims`, they can specify `requiredClaims` with the minimum list of claims needed, which will then be used for filtering and may result in additional IDP options being displayed to the customer. | Claim[] | no (`claims` will be used if no `requiredClaims`) | - |
37
37
  | label | The button label. Do not use 'Sign Up'. It is deprecated. | 'Verify' \| 'Login' \| 'Sign Up' \| 'Age check' \| 'Sign up' \| 'Authenticate' \| 'Checkout' | no | 'Verify' |
@@ -51,6 +51,24 @@ Your page should render a widget similar to this 👇 And when the button is cli
51
51
  | showInfoContent | The text that goes below the ConnectID button [More details here](#infoContent) | boolean | false | false |
52
52
  | onError | Callback to be called when there is an error fetching participants or invalid options. | () => void | no | () => {} // do nothing |
53
53
 
54
+ #### CompabilityWarnings type
55
+
56
+ ```typescript
57
+ export type CompabilityWarnings = {
58
+ mightFail: boolean
59
+ isMobile: boolean
60
+ isIncognito: boolean
61
+ mobileOs: 'android' | 'ios' | 'other'
62
+ browser: 'chrome' | 'samsung' | 'safari' | 'firefox' | 'other'
63
+ }
64
+ ```
65
+ It returns information about the compatibility of the user's browser and device with the ConnectID flow. It includes:
66
+ - `mightFail`: Indicates if the flow might fail due to compatibility issues.
67
+ - `isMobile`: Indicates if the user is on a mobile device.
68
+ - `isIncognito`: Indicates if the user is browsing in incognito mode.
69
+ - `mobileOs`: The operating system of the mobile device, if applicable.
70
+ - `browser`: The browser being used by the user.
71
+
54
72
  #### certificationStatus values
55
73
 
56
74
  - `Active`: only IDPs with active certification
@@ -223,6 +241,9 @@ The SVG image below the ConnectID button has either option dark or light mode. B
223
241
 
224
242
  ## 🚀 Release Notes
225
243
 
244
+ ### 6.10.0-alpha.0 (Jun 10, 2025)
245
+ - Add `compabilityWarnings` to `onProceed` callback.
246
+
226
247
  ### 6.9.0 (Jun 5, 2025)
227
248
  - Add new `introTextType` option: `verify-age`.
228
249
 
@@ -1,8 +1,9 @@
1
- import { AuthorisationServer, CertificationStatus, Claim, Device, RequiredCertification } from './types';
1
+ import { AuthorisationServer, AuthorisationServerCertification, CertificationProfileType, CertificationProfileVariant, CertificationStatus, Claim, Device, RequiredCertification } from './types';
2
2
  export declare const filterAuthorisationServers: (authorisationServers: AuthorisationServer[], showAllParticipants: boolean, certificationStatus: CertificationStatus, claims: Claim[], requiredCertifications?: RequiredCertification[]) => AuthorisationServer[];
3
3
  export declare const getManualAuthorisationServer: (authorisationServers: AuthorisationServer[], showAllParticipants: boolean) => AuthorisationServer | undefined;
4
4
  export declare const removeExpiredOrNotStartedCertifications: (authorisationServers: AuthorisationServer[]) => AuthorisationServer[];
5
5
  export declare const filterAuthorisationServerByClaims: (authorisationServers: AuthorisationServer[], claims: Claim[]) => AuthorisationServer[];
6
6
  export declare const filterAuthorisationServerByCertification: (authorisationServers: AuthorisationServer[], status: CertificationStatus, requiredCertifications?: RequiredCertification[]) => AuthorisationServer[];
7
7
  export declare const filterAuthorisationServerByDevice: (authorisationServers: AuthorisationServer[], deviceType: Device) => AuthorisationServer[];
8
+ export declare const hasCertification: (certifications: AuthorisationServerCertification[], certificationType: CertificationProfileType, certificationVariant: CertificationProfileVariant) => boolean;
8
9
  export declare const hasStarted: (startDateStr: string) => boolean;
package/certification.js CHANGED
@@ -82,6 +82,9 @@ export const filterAuthorisationServerByDevice = (authorisationServers, deviceTy
82
82
  const onlyAppAuthorisationServersIds = onlyAppAuthorisationServers.map((authorisationServer) => authorisationServer.AuthorisationServerId);
83
83
  return authorisationServers.filter((authorisationServer) => !onlyAppAuthorisationServersIds.includes(authorisationServer.AuthorisationServerId));
84
84
  };
85
+ export const hasCertification = (certifications, certificationType, certificationVariant) => {
86
+ return certifications.some(({ ProfileType, ProfileVariant }) => ProfileType === certificationType && ProfileVariant === certificationVariant);
87
+ };
85
88
  const isExpired = (expiryDateStr) => {
86
89
  const expiryDate = parseCertificationDate(expiryDateStr);
87
90
  expiryDate.setHours(23);
@@ -1,6 +1,6 @@
1
1
  import assert from 'node:assert';
2
2
  import { describe, it } from 'node:test';
3
- import { filterAuthorisationServerByClaims, filterAuthorisationServerByDevice, filterAuthorisationServers, getManualAuthorisationServer, removeExpiredOrNotStartedCertifications, } from './certification';
3
+ import { filterAuthorisationServerByClaims, filterAuthorisationServerByDevice, filterAuthorisationServers, getManualAuthorisationServer, hasCertification, removeExpiredOrNotStartedCertifications, } from './certification';
4
4
  describe('filterAuthorisationServers', () => {
5
5
  it('should return all authorisation servers if showAllParticipants is true regardless of certification status, required certifications and claims', () => {
6
6
  const authorisationServers = [
@@ -393,6 +393,35 @@ describe('filterAuthorisationServerByDevice', () => {
393
393
  ],
394
394
  },
395
395
  ];
396
- assert.deepStrictEqual(filterAuthorisationServerByDevice(authorisationServers, 'desktop'), [authorisationServers[1], authorisationServers[2]]);
396
+ assert.deepStrictEqual(filterAuthorisationServerByDevice(authorisationServers, 'desktop'), [
397
+ authorisationServers[1],
398
+ authorisationServers[2],
399
+ ]);
400
+ });
401
+ });
402
+ describe('hasCertification', () => {
403
+ it('should return true if certifications have channel app', () => {
404
+ const certifications = [
405
+ {
406
+ CertificationStartDate: '10/04/1900',
407
+ CertificationExpirationDate: '10/04/2099',
408
+ Status: 'Certified',
409
+ ProfileVariant: 'app',
410
+ ProfileType: 'Channel',
411
+ },
412
+ ];
413
+ assert.ok(hasCertification(certifications, 'Channel', 'app') === true);
414
+ });
415
+ it('should return false if certifications do not have channel app', () => {
416
+ const certifications = [
417
+ {
418
+ CertificationStartDate: '10/04/1900',
419
+ CertificationExpirationDate: '10/04/2099',
420
+ Status: 'Certified',
421
+ ProfileVariant: 'web',
422
+ ProfileType: 'Channel',
423
+ },
424
+ ];
425
+ assert.ok(hasCertification(certifications, 'Channel', 'app') === false);
397
426
  });
398
427
  });
@@ -0,0 +1,3 @@
1
+ import { CompabilityEnvironment, CompabilityWarnings } from './types';
2
+ export declare const getCompability: (env: CompabilityEnvironment) => CompabilityWarnings;
3
+ export declare const getMightFail: (env: Pick<CompabilityEnvironment, "idpHasAppCertification" | "isMobile" | "isIncognito" | "isAndroid" | "isIos" | "isSamsungBrowser" | "isChromeBrowser">) => boolean;
package/compability.js ADDED
@@ -0,0 +1,36 @@
1
+ import { getBrowserName } from './userAgent';
2
+ export const getCompability = (env) => {
3
+ const { idpHasAppCertification, isAndroid, isChromeBrowser, isIncognito, isMobile, isSamsungBrowser, isIos } = env;
4
+ const mightFail = getMightFail({
5
+ idpHasAppCertification,
6
+ isMobile,
7
+ isIncognito,
8
+ isAndroid,
9
+ isIos,
10
+ isSamsungBrowser,
11
+ isChromeBrowser,
12
+ });
13
+ return {
14
+ isMobile,
15
+ isIncognito,
16
+ mobileOs: isAndroid ? 'android' : isIos ? 'ios' : 'other',
17
+ browser: getBrowserName(),
18
+ mightFail,
19
+ };
20
+ };
21
+ export const getMightFail = (env) => {
22
+ const { idpHasAppCertification, isMobile, isIncognito, isAndroid, isChromeBrowser, isSamsungBrowser, isIos } = env;
23
+ if (!idpHasAppCertification || !isMobile) {
24
+ return false;
25
+ }
26
+ if (isIncognito && isAndroid && isChromeBrowser) {
27
+ return true;
28
+ }
29
+ if (isAndroid && isSamsungBrowser) {
30
+ return true;
31
+ }
32
+ if (isIncognito && isIos) {
33
+ return true;
34
+ }
35
+ return false;
36
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,97 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { getCompability, getMightFail } from './compability';
4
+ describe('getCompability', () => {
5
+ it('should return compability warnings', () => {
6
+ const compability = getCompability({
7
+ idpHasAppCertification: true,
8
+ isMobile: true,
9
+ isIncognito: false,
10
+ isAndroid: false,
11
+ isIos: false,
12
+ isSamsungBrowser: false,
13
+ isChromeBrowser: false,
14
+ });
15
+ assert.deepStrictEqual(compability, {
16
+ isMobile: true,
17
+ isIncognito: false,
18
+ mobileOs: 'other',
19
+ browser: 'other',
20
+ mightFail: false
21
+ });
22
+ });
23
+ });
24
+ describe('getMightFail', () => {
25
+ it('should return false when idpHasAppCertification is false', () => {
26
+ const mightFail = getMightFail({
27
+ idpHasAppCertification: false,
28
+ isMobile: true,
29
+ isIncognito: false,
30
+ isAndroid: false,
31
+ isIos: false,
32
+ isSamsungBrowser: false,
33
+ isChromeBrowser: false,
34
+ });
35
+ assert.ok(mightFail === false);
36
+ });
37
+ it('should return false when isMobile is false', () => {
38
+ const mightFail = getMightFail({
39
+ idpHasAppCertification: true,
40
+ isMobile: false,
41
+ isIncognito: false,
42
+ isAndroid: false,
43
+ isIos: false,
44
+ isSamsungBrowser: false,
45
+ isChromeBrowser: false,
46
+ });
47
+ assert.ok(mightFail === false);
48
+ });
49
+ it('should return true when isIncognito is true, isAndroid is true, and isChromeBrowser is true', () => {
50
+ const mightFail = getMightFail({
51
+ idpHasAppCertification: true,
52
+ isMobile: true,
53
+ isIncognito: true,
54
+ isAndroid: true,
55
+ isIos: false,
56
+ isSamsungBrowser: false,
57
+ isChromeBrowser: true,
58
+ });
59
+ assert.ok(mightFail === true);
60
+ });
61
+ it('should return true when isAndroid is true and isSamsungBrowser is true', () => {
62
+ const mightFail = getMightFail({
63
+ idpHasAppCertification: true,
64
+ isMobile: true,
65
+ isIncognito: false,
66
+ isAndroid: true,
67
+ isIos: false,
68
+ isSamsungBrowser: true,
69
+ isChromeBrowser: false,
70
+ });
71
+ assert.ok(mightFail === true);
72
+ });
73
+ it('should return true when isIncognito is true and isIos is true', () => {
74
+ const mightFail = getMightFail({
75
+ idpHasAppCertification: true,
76
+ isMobile: true,
77
+ isIncognito: true,
78
+ isAndroid: false,
79
+ isIos: true,
80
+ isSamsungBrowser: false,
81
+ isChromeBrowser: false,
82
+ });
83
+ assert.ok(mightFail === true);
84
+ });
85
+ it('should return false when none of the conditions are met', () => {
86
+ const mightFail = getMightFail({
87
+ idpHasAppCertification: true,
88
+ isMobile: true,
89
+ isIncognito: false,
90
+ isAndroid: false,
91
+ isIos: false,
92
+ isSamsungBrowser: false,
93
+ isChromeBrowser: false,
94
+ });
95
+ assert.ok(mightFail === false);
96
+ });
97
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@connectid-tools/idp-selector-react",
3
- "version": "6.9.0",
3
+ "version": "6.10.0-alpha.0",
4
4
  "description": "React component for IDP Selector.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -26,21 +26,21 @@
26
26
  "homepage": "https://github.com/connectid-tools/idp-selector-react",
27
27
  "devDependencies": {
28
28
  "@babel/preset-typescript": "^7.27.1",
29
- "@types/node": "^22.15.29",
29
+ "@types/node": "^22.15.31",
30
30
  "@types/react": "^18.3.23",
31
31
  "@types/react-dom": "^18.3.7",
32
- "cypress": "^13.17.0",
32
+ "cypress": "^14.4.1",
33
33
  "jest-environment-jsdom": "^29.7.0",
34
34
  "prettier": "^3.5.3",
35
35
  "react": "^18.3.1",
36
36
  "react-dom": "^18.3.1",
37
37
  "replace-in-file": "^8.3.0",
38
38
  "tsx": "^4.19.4",
39
- "typescript": "^5.8.3",
40
- "vite": "^5.4.19"
39
+ "typescript": "^5.8.3"
41
40
  },
42
41
  "dependencies": {
43
42
  "react-responsive": "^10.0.1",
44
- "swr": "^2.3.3"
43
+ "swr": "^2.3.3",
44
+ "vite": "^6.3.5"
45
45
  }
46
46
  }
package/types.d.ts CHANGED
@@ -5,7 +5,7 @@ export type CertificationProfileType = 'TDIF Accreditation' | 'Redirect' | 'Chan
5
5
  export type CertificationProfileVariant = 'FAPI2 Adv. OP w/Private Key, PAR' | 'Identity Provider' | 'Fallback Identity Service Provider' | 'web' | 'app' | 'name' | Claim;
6
6
  export type CertificationStatus = 'Active' | 'All';
7
7
  export type RequiredCertification = 'TDIF.IDP' | 'Redirect.FAPI2';
8
- type AuthorisationServerCertification = {
8
+ export type AuthorisationServerCertification = {
9
9
  Status: 'Certified' | 'Self-Certified' | 'Awaiting Certification' | 'Deprecated' | 'Rejected';
10
10
  ProfileType: CertificationProfileType;
11
11
  ProfileVariant: CertificationProfileVariant;
@@ -38,4 +38,19 @@ export type CidButtonTypeConfig = {
38
38
  hasHowItWorks: boolean;
39
39
  hasLearnMore: boolean;
40
40
  };
41
- export {};
41
+ export type CompabilityEnvironment = {
42
+ isMobile: boolean;
43
+ isIncognito: boolean;
44
+ idpHasAppCertification: boolean;
45
+ isAndroid: boolean;
46
+ isIos: boolean;
47
+ isSamsungBrowser: boolean;
48
+ isChromeBrowser: boolean;
49
+ };
50
+ export type CompabilityWarnings = {
51
+ mightFail: boolean;
52
+ isMobile: boolean;
53
+ isIncognito: boolean;
54
+ mobileOs: 'android' | 'ios' | 'other';
55
+ browser: 'chrome' | 'samsung' | 'safari' | 'firefox' | 'other';
56
+ };
package/userAgent.d.ts CHANGED
@@ -1,2 +1,7 @@
1
1
  import { Device } from './types';
2
2
  export declare const getDeviceType: () => Device;
3
+ export declare const isAndroid: () => boolean;
4
+ export declare const isIos: () => boolean;
5
+ export declare const isChromeBrowser: () => boolean;
6
+ export declare const isSamsungBrowser: () => boolean;
7
+ export declare const getBrowserName: () => "other" | "chrome" | "samsung" | "safari" | "firefox";
package/userAgent.js CHANGED
@@ -8,3 +8,23 @@ export const getDeviceType = () => {
8
8
  }
9
9
  return 'desktop';
10
10
  };
11
+ export const isAndroid = () => /Android/i.test(navigator.userAgent);
12
+ export const isIos = () => /iPhone|iPad|iPod/i.test(navigator.userAgent);
13
+ export const isChromeBrowser = () => /Chrome/.test(navigator.userAgent) && !/Edg|OPR|Brave/.test(navigator.userAgent);
14
+ export const isSamsungBrowser = () => /SamsungBrowser/.test(navigator.userAgent);
15
+ export const getBrowserName = () => {
16
+ const ua = navigator.userAgent.toLowerCase();
17
+ if (ua.includes('samsungbrowser')) {
18
+ return 'samsung';
19
+ }
20
+ if (ua.includes('firefox')) {
21
+ return 'firefox';
22
+ }
23
+ if (ua.includes('safari') && !ua.includes('chrome')) {
24
+ return 'safari';
25
+ }
26
+ if (ua.includes('chrome') || ua.includes('crios')) {
27
+ return 'chrome';
28
+ }
29
+ return 'other';
30
+ };