@opexa/portal-components 0.0.1123 → 0.0.1125

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.
@@ -4,5 +4,6 @@ import type { Authenticator, MutationConfig } from '../../types';
4
4
  import { type SignInInput } from '../services/signIn';
5
5
  export interface UseSignInMutationOptions extends MutationConfig<Authenticator | null, SignInInput> {
6
6
  versionSession?: VersionSession;
7
+ fingerprint?: () => Promise<string | null> | string | null;
7
8
  }
8
9
  export declare const useSignInMutation: (options?: UseSignInMutationOptions) => UseMutationResult<Authenticator | null, Error, SignInInput>;
@@ -1,7 +1,7 @@
1
1
  import { Capacitor } from '@capacitor/core';
2
2
  import { useMutation } from '@tanstack/react-query';
3
3
  import { useReCaptcha } from 'next-recaptcha-v3';
4
- import { RECAPTCHA_HEADER_KEY } from '../../constants/index.js';
4
+ import { FINGERPRINT_HEADER_KEY, RECAPTCHA_HEADER_KEY, } from '../../constants/index.js';
5
5
  import { getQueryClient } from '../../utils/getQueryClient.js';
6
6
  import { getSignInMutationKey } from '../../utils/mutationKeys.js';
7
7
  import { getSessionQueryKey } from '../../utils/queryKeys.js';
@@ -10,7 +10,7 @@ import { signIn } from '../services/signIn.js';
10
10
  export const useSignInMutation = (options) => {
11
11
  const queryClient = getQueryClient();
12
12
  const recaptcha = useReCaptcha();
13
- const { versionSession, ...config } = options ?? {};
13
+ const { fingerprint, versionSession, ...config } = options ?? {};
14
14
  return useMutation({
15
15
  ...config,
16
16
  mutationKey: getSignInMutationKey(),
@@ -19,6 +19,9 @@ export const useSignInMutation = (options) => {
19
19
  const token = recaptcha.reCaptchaKey
20
20
  ? await recaptcha.executeRecaptcha('submit')
21
21
  : null;
22
+ const fingerprintValue = versionSession === 'Inplay' && input.type === 'NAME_AND_PASSWORD'
23
+ ? await Promise.resolve(fingerprint?.()).catch(() => null)
24
+ : null;
22
25
  const authenticator = await signIn(input, {
23
26
  headers: {
24
27
  ...(token && {
@@ -27,6 +30,9 @@ export const useSignInMutation = (options) => {
27
30
  ...(session.domain && {
28
31
  Domain: session.domain,
29
32
  }),
33
+ ...(fingerprintValue && {
34
+ [FINGERPRINT_HEADER_KEY]: fingerprintValue,
35
+ }),
30
36
  ...(Capacitor.getPlatform() === 'android'
31
37
  ? { Channel: 'ANDROID' }
32
38
  : { Channel: 'IOS' }),
@@ -45,7 +45,7 @@ export function MobileNumberSignIn() {
45
45
  disclaimer: ctx.disclaimer,
46
46
  })));
47
47
  const signInMutation = useSignInMutation({
48
- versionSession: signInProps.versionSession,
48
+ // versionSession intentionally omitted: only applies to NAME_AND_PASSWORD
49
49
  onSuccess: async () => {
50
50
  step1Form.reset();
51
51
  step2Form.reset();
@@ -45,6 +45,7 @@ export function NameAndPasswordSignIn() {
45
45
  disclaimer: ctx.disclaimer,
46
46
  })));
47
47
  const signInMutation = useSignInMutation({
48
+ fingerprint: signInProps.fingerprint,
48
49
  versionSession: signInProps.versionSession,
49
50
  onSuccess: async (authenticator) => {
50
51
  if (authenticator) {
@@ -46,7 +46,7 @@ export function MobileNumberSignIn() {
46
46
  disclaimer: ctx.disclaimer,
47
47
  })));
48
48
  const signInMutation = useSignInMutation({
49
- versionSession: signInProps.versionSession,
49
+ // versionSession intentionally omitted: only applies to NAME_AND_PASSWORD
50
50
  onSuccess: async () => {
51
51
  step1Form.reset();
52
52
  step2Form.reset();
@@ -42,6 +42,7 @@ export function NameAndPasswordSignIn() {
42
42
  disclaimer: ctx.disclaimer,
43
43
  })));
44
44
  const signInMutation = useSignInMutation({
45
+ fingerprint: signInProps.fingerprint,
45
46
  versionSession: signInProps.versionSession,
46
47
  onSuccess: async (authenticator) => {
47
48
  if (authenticator) {
@@ -53,7 +53,7 @@ export function MobileNumberSignIn() {
53
53
  termsOfUse: ctx.termsOfUse,
54
54
  })));
55
55
  const signInMutation = useSignInMutation({
56
- versionSession: signInProps.versionSession,
56
+ // versionSession intentionally omitted: only applies to NAME_AND_PASSWORD
57
57
  onSuccess: async () => {
58
58
  step1Form.reset();
59
59
  step2Form.reset();
@@ -80,7 +80,7 @@ export function MobileNumberSignInInternational() {
80
80
  disclaimer: ctx.disclaimer,
81
81
  })));
82
82
  const signInMutation = useSignInMutation({
83
- versionSession: signInProps.versionSession,
83
+ // versionSession intentionally omitted: only applies to NAME_AND_PASSWORD
84
84
  onSuccess: async () => {
85
85
  step1Form.reset();
86
86
  step2Form.reset();
@@ -55,6 +55,7 @@ export function NameAndPasswordSignIn() {
55
55
  })));
56
56
  const [formType, setFormType] = useState('NAME_AND_PASSWORD');
57
57
  const signInMutation = useSignInMutation({
58
+ fingerprint: signInProps.fingerprint,
58
59
  versionSession: signInProps.versionSession,
59
60
  onSuccess: async (authenticator) => {
60
61
  if (authenticator) {
@@ -31,5 +31,10 @@ export interface SignInProps extends UseSignInProps {
31
31
  * - `Inplay`: `${AUTH_ENDPOINT}/v3/inplay/sessions`
32
32
  */
33
33
  versionSession?: VersionSession;
34
+ /**
35
+ * Optional browser fingerprint provider. For Inplay NAME_AND_PASSWORD
36
+ * sign-ins, pass `() => sdk.fingerprint()` from the consuming app.
37
+ */
38
+ fingerprint?: () => Promise<string | null> | string | null;
34
39
  }
35
40
  export declare function SignIn(props: SignInProps): import("react/jsx-runtime").JSX.Element;
@@ -30,11 +30,11 @@ export function UpdateMobilePhoneNumber() {
30
30
  const accountQuery = useAccountQuery();
31
31
  const account = accountQuery.data;
32
32
  const isAccountLoading = accountQuery.isLoading;
33
- const hasMobileNumber = !!account?.mobileNumber;
33
+ const isMobileNumberVerified = account?.mobileNumberVerified === true;
34
34
  const hasExecuted = useRef(false);
35
35
  useEffect(() => {
36
36
  if (!isAccountLoading && !!account && !hasExecuted.current) {
37
- if (!hasMobileNumber) {
37
+ if (!isMobileNumberVerified) {
38
38
  globalStore.updateMobilePhoneNumber.setOpen(true);
39
39
  }
40
40
  else {
@@ -45,7 +45,7 @@ export function UpdateMobilePhoneNumber() {
45
45
  }, [
46
46
  isAccountLoading,
47
47
  account,
48
- hasMobileNumber,
48
+ isMobileNumberVerified,
49
49
  globalStore.updateMobilePhoneNumber,
50
50
  ]);
51
51
  const [step, setStep] = useState(1);
@@ -2,3 +2,4 @@ export declare const RECAPTCHA_HEADER_KEY: string;
2
2
  export declare const TEST_PASS_HEADER_KEY: string;
3
3
  export declare const DOMAIN_HEADER_KEY: string;
4
4
  export declare const SESSION_VERSION_HEADER_KEY: string;
5
+ export declare const FINGERPRINT_HEADER_KEY: string;
@@ -2,3 +2,4 @@ export const RECAPTCHA_HEADER_KEY = 'Google-Recaptcha-Response';
2
2
  export const TEST_PASS_HEADER_KEY = 'Test-Pass';
3
3
  export const DOMAIN_HEADER_KEY = 'Domain';
4
4
  export const SESSION_VERSION_HEADER_KEY = 'X-Session-Version';
5
+ export const FINGERPRINT_HEADER_KEY = 'Fingerprint';
@@ -1,7 +1,7 @@
1
1
  import { addDays, addMinutes, subMinutes } from 'date-fns';
2
2
  import { NextResponse } from 'next/server';
3
3
  import { z } from 'zod';
4
- import { ACCESS_TOKEN_COOKIE_NAME, DOMAIN_HEADER_KEY, RECAPTCHA_HEADER_KEY, REFRESH_TOKEN_COOKIE_NAME, SESSION_VERSION_HEADER_KEY, } from '../constants/index.js';
4
+ import { ACCESS_TOKEN_COOKIE_NAME, DOMAIN_HEADER_KEY, FINGERPRINT_HEADER_KEY, RECAPTCHA_HEADER_KEY, REFRESH_TOKEN_COOKIE_NAME, SESSION_VERSION_HEADER_KEY, } from '../constants/index.js';
5
5
  import { createSession } from '../services/auth.js';
6
6
  const CreateSessionDefinition = z.union([
7
7
  z.object({
@@ -41,6 +41,7 @@ export async function postSession(request) {
41
41
  }
42
42
  const recaptcha = request.headers.get(RECAPTCHA_HEADER_KEY);
43
43
  const domain = request.headers.get(DOMAIN_HEADER_KEY);
44
+ const fingerprint = request.headers.get(FINGERPRINT_HEADER_KEY);
44
45
  const versionSessionHeader = request.headers.get(SESSION_VERSION_HEADER_KEY);
45
46
  const versionSession = versionSessionHeader === 'Inplay' ? 'Inplay' : 'default';
46
47
  try {
@@ -52,6 +53,9 @@ export async function postSession(request) {
52
53
  ...(domain && {
53
54
  [DOMAIN_HEADER_KEY]: domain,
54
55
  }),
56
+ ...(fingerprint && {
57
+ [FINGERPRINT_HEADER_KEY]: fingerprint,
58
+ }),
55
59
  ...(userAgent && {
56
60
  'User-Agent': userAgent,
57
61
  }),
@@ -1,4 +1,4 @@
1
- import { AUTH_ENDPOINT } from '../constants/index.js';
1
+ import { AUTH_ENDPOINT, FINGERPRINT_HEADER_KEY } from '../constants/index.js';
2
2
  import { httpRequest } from './httpRequest.js';
3
3
  import { sha256 } from './sha256.js';
4
4
  const SESSION_ENDPOINTS = {
@@ -29,8 +29,42 @@ export async function createSession(input, options, versionSession = 'default')
29
29
  new Headers(options?.headers).forEach((value, key) => {
30
30
  headers.set(key, value);
31
31
  });
32
+ // `versionSession` only applies to NAME_AND_PASSWORD sign-ins. All other
33
+ // auth types (MOBILE_NUMBER, MAYA, SOCIALS, CABINET, SINGLE_USE_TOKEN) are
34
+ // forced to the default `/sessions` endpoint.
35
+ const effectiveVersionSession = input.type === 'NAME_AND_PASSWORD' ? versionSession : 'default';
36
+ // The `Fingerprint` header is only meaningful for the Inplay
37
+ // NAME_AND_PASSWORD endpoint (`/v3/inplay/sessions`). Strip it for any
38
+ // other combination to avoid leaking it to upstream endpoints that don't
39
+ // expect it.
40
+ if (effectiveVersionSession !== 'Inplay' ||
41
+ input.type !== 'NAME_AND_PASSWORD') {
42
+ headers.delete(FINGERPRINT_HEADER_KEY);
43
+ }
32
44
  try {
33
- return await httpRequest.json(`${AUTH_ENDPOINT}${SESSION_ENDPOINTS[versionSession]}`, {
45
+ // The Inplay `/v3/inplay/sessions` endpoint wraps its payload in
46
+ // `{ data: { ... } }` (same shape as `/v2/sessions`), while the
47
+ // default `/sessions` endpoint returns the flat shape directly.
48
+ // Unwrap the Inplay response so callers can treat both consistently
49
+ // via the existing `CreateSessionMutation` union.
50
+ if (effectiveVersionSession === 'Inplay') {
51
+ const wrapped = await httpRequest.json(`${AUTH_ENDPOINT}${SESSION_ENDPOINTS[effectiveVersionSession]}`, {
52
+ ...options,
53
+ method: 'POST',
54
+ headers,
55
+ });
56
+ if (wrapped.data.authenticator) {
57
+ return {
58
+ authenticator: wrapped.data.authenticator,
59
+ };
60
+ }
61
+ return {
62
+ session: wrapped.data.session,
63
+ accessToken: wrapped.data.accessToken,
64
+ refreshToken: wrapped.data.refreshToken,
65
+ };
66
+ }
67
+ return await httpRequest.json(`${AUTH_ENDPOINT}${SESSION_ENDPOINTS[effectiveVersionSession]}`, {
34
68
  ...options,
35
69
  method: 'POST',
36
70
  headers,