@quiltt/react 4.0.0 → 4.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @quiltt/react
2
2
 
3
+ ## 4.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#342](https://github.com/quiltt/quiltt-js/pull/342) [`6a387ba`](https://github.com/quiltt/quiltt-js/commit/6a387ba0db77912df85c6cd1924f63edf50f9cdd) Thanks [@sirwolfgang](https://github.com/sirwolfgang)! - Create useQuilttInstitutions hook
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [[`6a387ba`](https://github.com/quiltt/quiltt-js/commit/6a387ba0db77912df85c6cd1924f63edf50f9cdd)]:
12
+ - @quiltt/core@4.1.0
13
+
14
+ ## 4.0.1
15
+
16
+ ### Patch Changes
17
+
18
+ - [#343](https://github.com/quiltt/quiltt-js/pull/343) [`da152b7`](https://github.com/quiltt/quiltt-js/commit/da152b7f42606defde5f55488632bcdc095be592) Thanks [@sirwolfgang](https://github.com/sirwolfgang)! - Add the ability to set nonce for the Quiltt SDK Script
19
+
20
+ - Updated dependencies [[`da152b7`](https://github.com/quiltt/quiltt-js/commit/da152b7f42606defde5f55488632bcdc095be592)]:
21
+ - @quiltt/core@4.0.1
22
+
3
23
  ## 4.0.0
4
24
 
5
25
  ### Major Changes
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Maybe, QuilttJWT, UsernamePayload, UnprocessableData, AuthAPI, PasscodePayload, ConnectorSDKConnectorOptions, ConnectorSDKCallbacks } from '@quiltt/core';
2
2
  export * from '@quiltt/core';
3
- import { RefObject, useLayoutEffect, Dispatch, SetStateAction, FC, PropsWithChildren, JSX, ComponentType, ElementType, MouseEvent } from 'react';
3
+ import { RefObject, useEffect, Dispatch, SetStateAction, FC, PropsWithChildren, JSX, ComponentType, ElementType, MouseEvent } from 'react';
4
4
  import { useApolloClient } from '@apollo/client/react/hooks/useApolloClient.js';
5
5
  import * as react_jsx_runtime from 'react/jsx-runtime';
6
6
 
@@ -13,7 +13,7 @@ declare function useEventListener<K extends keyof DocumentEventMap>(eventName: K
13
13
  * This hook is a browser hook. But React code could be generated from the server without the Window API.
14
14
  * This hook switches between useEffect and useLayoutEffect following the execution environment.
15
15
  */
16
- declare const useIsomorphicLayoutEffect: typeof useLayoutEffect;
16
+ declare const useIsomorphicLayoutEffect: typeof useEffect;
17
17
 
18
18
  type SetSession = Dispatch<SetStateAction<Maybe<string> | undefined>>;
19
19
  /**
@@ -153,4 +153,5 @@ type QuilttContainerProps<T extends ElementType> = PropsWithChildren<{
153
153
  */
154
154
  declare const QuilttContainer: <T extends ElementType = "div">({ as, connectorId, connectionId, onEvent, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, children, ...props }: QuilttContainerProps<T> & PropsOf<T>) => react_jsx_runtime.JSX.Element;
155
155
 
156
- export { type AuthenticateSession, type IdentifySession, type ImportSession, QuilttAuthProvider, QuilttButton, QuilttContainer, QuilttProvider, QuilttSettingsProvider, type RevokeSession, type SetSession, type UseQuilttSession, useAuthenticateSession, useEventListener, useIdentifySession, useImportSession, useIsomorphicLayoutEffect, useQuilttClient, useQuilttConnector, useQuilttSession, useQuilttSettings, useRevokeSession, useSession, useStorage };
156
+ export { QuilttAuthProvider, QuilttButton, QuilttContainer, QuilttProvider, QuilttSettingsProvider, useAuthenticateSession, useEventListener, useIdentifySession, useImportSession, useIsomorphicLayoutEffect, useQuilttClient, useQuilttConnector, useQuilttSession, useQuilttSettings, useRevokeSession, useSession, useStorage };
157
+ export type { AuthenticateSession, IdentifySession, ImportSession, RevokeSession, SetSession, UseQuilttSession };
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ export { u as useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect-clie
4
4
  import { Q as QuilttAuthProvider } from './QuilttAuthProvider-client-CER-TOln.js';
5
5
  export { b as useAuthenticateSession, a as useIdentifySession, u as useImportSession, c as useRevokeSession } from './QuilttAuthProvider-client-CER-TOln.js';
6
6
  export { u as useQuilttClient } from './useQuilttClient-client-CAAUait1.js';
7
- import { u as useQuilttConnector } from './useQuilttConnector-client-CGhW0L-V.js';
7
+ import { u as useQuilttConnector } from './useQuilttConnector-client-BB7kw8vs.js';
8
8
  export { u as useQuilttSession } from './useQuilttSession-client-D2mjVT4S.js';
9
9
  export { u as useQuilttSettings } from './useQuilttSettings-client-BOCBjFXe.js';
10
10
  export { u as useSession } from './useSession-client-CCAvnROP.js';
@@ -26,6 +26,7 @@ const QuilttButton = ({ as, connectorId, connectionId, institution, onEvent, onO
26
26
  const { open } = useQuilttConnector(connectorId, {
27
27
  connectionId,
28
28
  institution,
29
+ nonce: props?.nonce,
29
30
  onEvent,
30
31
  onOpen,
31
32
  onLoad,
@@ -58,6 +59,7 @@ const QuilttButton = ({ as, connectorId, connectionId, institution, onEvent, onO
58
59
  * one Container at a time.
59
60
  */ const QuilttContainer = ({ as, connectorId, connectionId, onEvent, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, children, ...props })=>{
60
61
  useQuilttConnector(connectorId, {
62
+ nonce: props?.nonce,
61
63
  onEvent,
62
64
  onLoad,
63
65
  onExit,
@@ -2,13 +2,17 @@
2
2
  import { useState, useEffect, useCallback } from 'react';
3
3
  import { cdnBase } from '@quiltt/core';
4
4
  import { u as useQuilttSession } from './useQuilttSession-client-D2mjVT4S.js';
5
- import { u as useScript } from './useScript-client-CWRBlZBC.js';
5
+ import { u as useScript } from './useScript-client-JCgaTW9n.js';
6
6
 
7
- var version = "4.0.0";
7
+ var version = "4.1.0";
8
8
 
9
9
  const useQuilttConnector = (connectorId, options)=>{
10
- const status = useScript(`${cdnBase}/v1/connector.js?agent=react-${version}`);
11
- const { session } = useQuilttSession();
10
+ const status = useScript(`${cdnBase}/v1/connector.js?agent=react-${version}`, {
11
+ nonce: options?.nonce
12
+ });
13
+ // This ensures we're not destructuring `session` before the script has loaded
14
+ const useQuilttSessionReturn = useQuilttSession();
15
+ const session = useQuilttSessionReturn?.session || null;
12
16
  const [connector, setConnector] = useState();
13
17
  const [isOpening, setIsOpening] = useState(false);
14
18
  // Set Session
@@ -42,6 +42,9 @@ function useScript(src, options) {
42
42
  scriptNode = document.createElement('script');
43
43
  scriptNode.src = src;
44
44
  scriptNode.async = true;
45
+ if (options?.nonce && options.nonce.trim() !== '') {
46
+ scriptNode.nonce = options.nonce;
47
+ }
45
48
  scriptNode.setAttribute('data-status', 'loading');
46
49
  document.body.appendChild(scriptNode);
47
50
  // Store status in attribute on script
@@ -73,10 +76,14 @@ function useScript(src, options) {
73
76
  scriptNode.removeEventListener('load', setStateFromEvent);
74
77
  scriptNode.removeEventListener('error', setStateFromEvent);
75
78
  }
79
+ if (scriptNode && options?.removeOnUnmount) {
80
+ scriptNode.remove();
81
+ }
76
82
  };
77
83
  }, [
78
84
  src,
79
85
  options?.shouldPreventLoad,
86
+ options?.nonce,
80
87
  options?.removeOnUnmount
81
88
  ]);
82
89
  return status;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quiltt/react",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "description": "React Components and Hooks for Quiltt Connector",
5
5
  "keywords": [
6
6
  "quiltt",
@@ -35,18 +35,19 @@
35
35
  "main": "dist/index.js",
36
36
  "dependencies": {
37
37
  "@apollo/client": "^3.12.4",
38
- "@quiltt/core": "4.0.0"
38
+ "use-debounce": "^10.0.4",
39
+ "@quiltt/core": "4.1.0"
39
40
  },
40
41
  "devDependencies": {
41
42
  "@biomejs/biome": "1.9.4",
42
- "@types/node": "22.13.10",
43
- "@types/react": "18.3.12",
44
- "@types/react-dom": "18.3.1",
43
+ "@types/node": "22.14.1",
44
+ "@types/react": "18.3.20",
45
+ "@types/react-dom": "18.3.5",
45
46
  "bunchee": "6.3.4",
46
47
  "react": "18.3.1",
47
48
  "react-dom": "18.3.1",
48
49
  "rimraf": "6.0.1",
49
- "typescript": "5.7.3"
50
+ "typescript": "5.8.2"
50
51
  },
51
52
  "peerDependencies": {
52
53
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
@@ -47,6 +47,7 @@ export const QuilttButton = <T extends ElementType = 'button'>({
47
47
  const { open } = useQuilttConnector(connectorId, {
48
48
  connectionId,
49
49
  institution,
50
+ nonce: props?.nonce, // Pass nonce for script loading if needed
50
51
  onEvent,
51
52
  onOpen,
52
53
  onLoad,
@@ -31,6 +31,7 @@ export const QuilttContainer = <T extends ElementType = 'div'>({
31
31
  ...props
32
32
  }: QuilttContainerProps<T> & PropsOf<T>) => {
33
33
  useQuilttConnector(connectorId, {
34
+ nonce: props?.nonce, // Pass nonce for script loading if needed
34
35
  onEvent,
35
36
  onLoad,
36
37
  onExit,
@@ -19,8 +19,14 @@ export const useQuilttConnector = (
19
19
  connectorId?: string,
20
20
  options?: ConnectorSDKConnectorOptions
21
21
  ) => {
22
- const status = useScript(`${cdnBase}/v1/connector.js?agent=react-${version}`)
23
- const { session } = useQuilttSession()
22
+ const status = useScript(`${cdnBase}/v1/connector.js?agent=react-${version}`, {
23
+ nonce: options?.nonce,
24
+ })
25
+
26
+ // This ensures we're not destructuring `session` before the script has loaded
27
+ const useQuilttSessionReturn = useQuilttSession()
28
+ const session = useQuilttSessionReturn?.session || null
29
+
24
30
  const [connector, setConnector] = useState<ConnectorSDKConnector>()
25
31
  const [isOpening, setIsOpening] = useState<boolean>(false)
26
32
 
@@ -0,0 +1,124 @@
1
+ 'use client'
2
+
3
+ import { useCallback, useEffect, useMemo, useState } from 'react'
4
+
5
+ import { useDebounce } from 'use-debounce'
6
+
7
+ import { InstitutionsAPI } from '@quiltt/core'
8
+ import type { ErrorData, InstitutionsData } from '@quiltt/core'
9
+
10
+ import { version } from '@/version'
11
+ import useSession from './useSession'
12
+
13
+ export type UseQuilttInstitutions = (
14
+ connectorId: string,
15
+ onErrorCallback?: (msg: string) => void
16
+ ) => {
17
+ searchTerm: string
18
+ searchResults: InstitutionsData
19
+ isSearching: boolean
20
+ setSearchTerm: (term: string) => void
21
+ }
22
+
23
+ export const useQuilttInstitutions: UseQuilttInstitutions = (connectorId, onErrorCallback) => {
24
+ const agent = useMemo(() => {
25
+ // Try deprecated navigator.product first (still used in some RN versions)
26
+ if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
27
+ return `react-native-${version}`
28
+ }
29
+
30
+ // Detect React Native by its unique environment characteristics
31
+ const isReactNative = !!(
32
+ // Has window (unlike Node.js)
33
+ (
34
+ typeof window !== 'undefined' &&
35
+ // No document in window (unlike browsers)
36
+ typeof window.document === 'undefined' &&
37
+ // Has navigator (unlike Node.js)
38
+ typeof navigator !== 'undefined'
39
+ )
40
+ )
41
+
42
+ return isReactNative ? `react-native-${version}` : `react-${version}`
43
+ }, [])
44
+
45
+ const institutionsAPI = useMemo(
46
+ () => new InstitutionsAPI(connectorId, agent),
47
+ [connectorId, agent]
48
+ )
49
+ const [session] = useSession()
50
+
51
+ const [searchTermInput, setSearchTermInput] = useState('')
52
+ const [searchTerm] = useDebounce(searchTermInput, 350)
53
+ const [searchResults, setSearchResults] = useState<InstitutionsData>([])
54
+
55
+ const [isSearching, setIsSearching] = useState(false)
56
+
57
+ /**
58
+ * Start Search
59
+ * This function is used to initiate a search for institutions based on the provided term with
60
+ * a minimum length of 2 characters. Debouncing is applied to avoid excessive API calls.
61
+ */
62
+ const startSearch = useCallback((term: string) => {
63
+ if (term.trim().length < 2) {
64
+ setSearchResults([])
65
+ setIsSearching(false)
66
+ return
67
+ }
68
+
69
+ setIsSearching(true)
70
+ setSearchTermInput(term)
71
+ }, [])
72
+
73
+ const handleError = useCallback(
74
+ (message: string) => {
75
+ const errorMessage = message || 'Unknown error occurred while searching institutions'
76
+
77
+ console.error('Quiltt Institutions Search Error:', errorMessage)
78
+ if (onErrorCallback) onErrorCallback(errorMessage)
79
+ },
80
+ [onErrorCallback]
81
+ )
82
+
83
+ /**
84
+ * Run Search
85
+ * This effect will run when the searchTerm changes and is at least 2 characters long.
86
+ */
87
+ useEffect(() => {
88
+ if (!session?.token || !connectorId || !searchTerm || searchTerm.trim().length < 2) {
89
+ return
90
+ }
91
+
92
+ const abortController = new AbortController()
93
+
94
+ institutionsAPI
95
+ .search(session?.token, connectorId, searchTerm, abortController.signal)
96
+ .then((response) => {
97
+ if (!abortController.signal.aborted) {
98
+ if (response.status === 200) {
99
+ setSearchResults(response.data as InstitutionsData)
100
+ } else {
101
+ handleError((response.data as ErrorData).message || 'Failed to fetch institutions')
102
+ }
103
+ setIsSearching(false)
104
+ }
105
+ })
106
+ .catch((error) => {
107
+ if (!abortController.signal.aborted) {
108
+ handleError(error.message)
109
+ setIsSearching(false)
110
+ }
111
+ })
112
+
113
+ return () => abortController.abort()
114
+ }, [session?.token, connectorId, searchTerm, institutionsAPI, handleError])
115
+
116
+ return {
117
+ searchTerm,
118
+ searchResults,
119
+ isSearching,
120
+ setSearchTerm: startSearch,
121
+ }
122
+ }
123
+
124
+ export default useQuilttInstitutions
@@ -6,6 +6,7 @@ export type UseScriptStatus = 'idle' | 'loading' | 'ready' | 'error'
6
6
  export interface UseScriptOptions {
7
7
  shouldPreventLoad?: boolean
8
8
  removeOnUnmount?: boolean
9
+ nonce?: string
9
10
  }
10
11
 
11
12
  // Cached script statuses
@@ -58,6 +59,9 @@ export function useScript(src: string | null, options?: UseScriptOptions): UseSc
58
59
  scriptNode = document.createElement('script')
59
60
  scriptNode.src = src
60
61
  scriptNode.async = true
62
+ if (options?.nonce && options.nonce.trim() !== '') {
63
+ scriptNode.nonce = options.nonce
64
+ }
61
65
  scriptNode.setAttribute('data-status', 'loading')
62
66
  document.body.appendChild(scriptNode)
63
67
 
@@ -100,7 +104,7 @@ export function useScript(src: string | null, options?: UseScriptOptions): UseSc
100
104
  scriptNode.remove()
101
105
  }
102
106
  }
103
- }, [src, options?.shouldPreventLoad, options?.removeOnUnmount])
107
+ }, [src, options?.shouldPreventLoad, options?.nonce, options?.removeOnUnmount])
104
108
 
105
109
  return status
106
110
  }