@quiltt/react 3.9.3 → 3.9.5

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 (27) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/{QuilttAuthProvider-client-D6Iok6TS.js → QuilttAuthProvider-client-CER-TOln.js} +3 -3
  3. package/dist/QuilttSettings-client-BK-0SQME.js +6 -0
  4. package/dist/{QuilttSettingsProvider-client-EfyHgg4i.js → QuilttSettingsProvider-client-Va7uJ_dQ.js} +1 -4
  5. package/dist/index.d.ts +10 -16
  6. package/dist/index.js +8 -8
  7. package/dist/{useQuilttConnector-client-720xcXap.js → useQuilttConnector-client-CwD2X-Ms.js} +2 -2
  8. package/dist/{useQuilttSession-client-KZRjdl5I.js → useQuilttSession-client-D2mjVT4S.js} +3 -3
  9. package/dist/useQuilttSettings-client-BOCBjFXe.js +10 -0
  10. package/dist/useSession-client-CCAvnROP.js +69 -0
  11. package/dist/{useStorage-client-B3keU-oI.js → useStorage-client-DHcq3Kuh.js} +22 -13
  12. package/package.json +4 -4
  13. package/src/components/QuilttButton.tsx +2 -2
  14. package/src/components/QuilttContainer.tsx +2 -2
  15. package/src/contexts/QuilttSettings.ts +9 -0
  16. package/src/contexts/index.ts +1 -0
  17. package/src/hooks/session/useAuthenticateSession.ts +1 -1
  18. package/src/hooks/session/useIdentifySession.ts +1 -1
  19. package/src/hooks/session/useImportSession.ts +1 -1
  20. package/src/hooks/session/useRevokeSession.ts +1 -1
  21. package/src/hooks/useQuilttConnector.ts +3 -4
  22. package/src/hooks/useQuilttSettings.ts +3 -7
  23. package/src/hooks/useSession.ts +25 -15
  24. package/src/hooks/useStorage.ts +18 -16
  25. package/src/providers/QuilttSettingsProvider.tsx +1 -1
  26. package/dist/useQuilttSettings-client-DU_Qfc8X.js +0 -12
  27. package/dist/useSession-client-CG5lGS9F.js +0 -60
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @quiltt/react
2
2
 
3
+ ## 3.9.5
4
+
5
+ ### Patch Changes
6
+
7
+ - [#325](https://github.com/quiltt/quiltt-js/pull/325) [`62b7323`](https://github.com/quiltt/quiltt-js/commit/62b732371a8d57242170e0ae838baa4ca8e78059) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Improve useSession and useStorage hooks
8
+
9
+ - Updated dependencies [[`62b7323`](https://github.com/quiltt/quiltt-js/commit/62b732371a8d57242170e0ae838baa4ca8e78059)]:
10
+ - @quiltt/core@3.9.5
11
+
12
+ ## 3.9.4
13
+
14
+ ### Patch Changes
15
+
16
+ - [#321](https://github.com/quiltt/quiltt-js/pull/321) [`642ec0f`](https://github.com/quiltt/quiltt-js/commit/642ec0f34f2506672993b82785b5b5ddb5c69069) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Bugfix: iOS WebView header drag behavior and scrolling
17
+
18
+ - Updated dependencies [[`642ec0f`](https://github.com/quiltt/quiltt-js/commit/642ec0f34f2506672993b82785b5b5ddb5c69069)]:
19
+ - @quiltt/core@3.9.4
20
+
3
21
  ## 3.9.3
4
22
 
5
23
  ### Patch Changes
@@ -2,11 +2,11 @@
2
2
  import { useCallback, useRef, useMemo, useEffect } from 'react';
3
3
  import { JsonWebTokenParse, QuilttClient, InMemoryCache } from '@quiltt/core';
4
4
  import '@apollo/client/react/hooks/useApolloClient.js';
5
- import './useQuilttSettings-client-DU_Qfc8X.js';
6
- import './useSession-client-CG5lGS9F.js';
5
+ import './QuilttSettings-client-BK-0SQME.js';
6
+ import './useSession-client-CCAvnROP.js';
7
7
  import { jsx } from 'react/jsx-runtime';
8
8
  import { ApolloProvider } from '@apollo/client/react/context/ApolloProvider.js';
9
- import { u as useQuilttSession } from './useQuilttSession-client-KZRjdl5I.js';
9
+ import { u as useQuilttSession } from './useQuilttSession-client-D2mjVT4S.js';
10
10
 
11
11
  const useIdentifySession = (auth, setSession)=>{
12
12
  const identifySession = useCallback(async (payload, callbacks)=>{
@@ -0,0 +1,6 @@
1
+ 'use client';
2
+ import { createContext } from 'react';
3
+
4
+ const QuilttSettings = createContext({});
5
+
6
+ export { QuilttSettings as Q };
@@ -1,10 +1,7 @@
1
1
  'use client';
2
2
  import { jsx } from 'react/jsx-runtime';
3
3
  import { useState } from 'react';
4
- import '@quiltt/core';
5
- import '@apollo/client/react/hooks/useApolloClient.js';
6
- import { Q as QuilttSettings } from './useQuilttSettings-client-DU_Qfc8X.js';
7
- import './useSession-client-CG5lGS9F.js';
4
+ import { Q as QuilttSettings } from './QuilttSettings-client-BK-0SQME.js';
8
5
 
9
6
  const QuilttSettingsProvider = ({ clientId, children })=>{
10
7
  const [_clientId] = useState(clientId);
package/dist/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Maybe, QuilttJWT, UsernamePayload, UnprocessableData, AuthAPI, PasscodePayload, ConnectorSDKConnectorOptions, ConnectorSDKCallbacks } from '@quiltt/core';
2
2
  export * from '@quiltt/core';
3
- import * as react from 'react';
4
3
  import { RefObject, useLayoutEffect, Dispatch, SetStateAction, FC, PropsWithChildren, JSX, ComponentType, ElementType, MouseEvent } from 'react';
5
4
  import { useApolloClient } from '@apollo/client/react/hooks/useApolloClient.js';
6
5
  import * as react_jsx_runtime from 'react/jsx-runtime';
@@ -18,18 +17,17 @@ declare const useIsomorphicLayoutEffect: typeof useLayoutEffect;
18
17
 
19
18
  type SetSession = Dispatch<SetStateAction<Maybe<string> | undefined>>;
20
19
  /**
21
- * useSession uses useStorage to support a global singleton style of access. When
22
- * updated, all components, and windows should also invalidate.
20
+ * Custom hook to manage JWT session state with automatic expiration handling.
21
+ * Provides global singleton access to the session across components and windows.
23
22
  *
24
23
  * TODO: Support Rotation before Expiry
25
24
  *
26
- * Dataflow can come from two directions:
27
- * 1. Login - Bottom Up
28
- * This happens on login, when a token is passed up through the setSession
29
- * callback. From here it needs to be stored, and shared for usage.
30
- * 2. Refresh - Top Down
31
- * This happens when a page is reloaded or a person returns, and everything is
32
- * reinitialized.
25
+ * Handles two types of data flow:
26
+ * 1. Bottom-up (Login): Token passed through setSession callback
27
+ * 2. Top-down (Refresh): State reinitialized on page reload
28
+ *
29
+ * @param storageKey - Key used for storing session in useStorage (defaults to 'session')
30
+ * @returns [session, setSession] - Current session state and setter function
33
31
  */
34
32
  declare const useSession: (storageKey?: string) => [Maybe<QuilttJWT> | undefined, SetSession];
35
33
 
@@ -78,12 +76,8 @@ type UseQuilttSession = (environmentId?: string) => {
78
76
  };
79
77
  declare const useQuilttSession: UseQuilttSession;
80
78
 
81
- type QuilttSettingsContext = {
82
- clientId?: string | undefined;
83
- };
84
- declare const QuilttSettings: react.Context<QuilttSettingsContext>;
85
79
  declare const useQuilttSettings: () => {
86
- clientId?: string | undefined;
80
+ clientId?: string;
87
81
  };
88
82
 
89
83
  /**
@@ -159,4 +153,4 @@ type QuilttContainerProps<T extends ElementType> = PropsWithChildren<{
159
153
  */
160
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;
161
155
 
162
- export { type AuthenticateSession, type IdentifySession, type ImportSession, QuilttAuthProvider, QuilttButton, QuilttContainer, QuilttProvider, QuilttSettings, QuilttSettingsProvider, type RevokeSession, type SetSession, type UseQuilttSession, useAuthenticateSession, useEventListener, useIdentifySession, useImportSession, useIsomorphicLayoutEffect, useQuilttClient, useQuilttConnector, useQuilttSession, useQuilttSettings, useRevokeSession, useSession, useStorage };
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 };
package/dist/index.js CHANGED
@@ -1,16 +1,16 @@
1
1
  export * from '@quiltt/core';
2
2
  export { u as useEventListener } from './useEventListener-client-DVM5xwKY.js';
3
3
  export { u as useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect-client-DeTHOKz1.js';
4
- import { Q as QuilttAuthProvider } from './QuilttAuthProvider-client-D6Iok6TS.js';
5
- export { b as useAuthenticateSession, a as useIdentifySession, u as useImportSession, c as useRevokeSession } from './QuilttAuthProvider-client-D6Iok6TS.js';
4
+ import { Q as QuilttAuthProvider } from './QuilttAuthProvider-client-CER-TOln.js';
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-720xcXap.js';
8
- export { u as useQuilttSession } from './useQuilttSession-client-KZRjdl5I.js';
9
- export { Q as QuilttSettings, u as useQuilttSettings } from './useQuilttSettings-client-DU_Qfc8X.js';
10
- export { u as useSession } from './useSession-client-CG5lGS9F.js';
11
- export { u as useStorage } from './useStorage-client-B3keU-oI.js';
7
+ import { u as useQuilttConnector } from './useQuilttConnector-client-CwD2X-Ms.js';
8
+ export { u as useQuilttSession } from './useQuilttSession-client-D2mjVT4S.js';
9
+ export { u as useQuilttSettings } from './useQuilttSettings-client-BOCBjFXe.js';
10
+ export { u as useSession } from './useSession-client-CCAvnROP.js';
11
+ export { u as useStorage } from './useStorage-client-DHcq3Kuh.js';
12
12
  import { jsx } from 'react/jsx-runtime';
13
- import { Q as QuilttSettingsProvider } from './QuilttSettingsProvider-client-EfyHgg4i.js';
13
+ import { Q as QuilttSettingsProvider } from './QuilttSettingsProvider-client-Va7uJ_dQ.js';
14
14
 
15
15
  const QuilttProvider = ({ clientId, token, children })=>{
16
16
  return /*#__PURE__*/ jsx(QuilttSettingsProvider, {
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
  import { useState, useEffect, useCallback } from 'react';
3
3
  import { cdnBase } from '@quiltt/core';
4
- import { u as useQuilttSession } from './useQuilttSession-client-KZRjdl5I.js';
4
+ import { u as useQuilttSession } from './useQuilttSession-client-D2mjVT4S.js';
5
5
  import { u as useScript } from './useScript-client-CWRBlZBC.js';
6
6
 
7
- var version = "3.9.3";
7
+ var version = "3.9.5";
8
8
 
9
9
  const useQuilttConnector = (connectorId, options)=>{
10
10
  const status = useScript(`${cdnBase}/v1/connector.js?agent=react-${version}`);
@@ -1,9 +1,9 @@
1
1
  'use client';
2
2
  import { useCallback } from 'react';
3
3
  import { AuthAPI } from '@quiltt/core';
4
- import { u as useImportSession, a as useIdentifySession, b as useAuthenticateSession, c as useRevokeSession } from './QuilttAuthProvider-client-D6Iok6TS.js';
5
- import { u as useQuilttSettings } from './useQuilttSettings-client-DU_Qfc8X.js';
6
- import { u as useSession } from './useSession-client-CG5lGS9F.js';
4
+ import { u as useImportSession, a as useIdentifySession, b as useAuthenticateSession, c as useRevokeSession } from './QuilttAuthProvider-client-CER-TOln.js';
5
+ import { u as useQuilttSettings } from './useQuilttSettings-client-BOCBjFXe.js';
6
+ import { u as useSession } from './useSession-client-CCAvnROP.js';
7
7
 
8
8
  const useQuilttSession = (environmentId)=>{
9
9
  const { clientId } = useQuilttSettings();
@@ -0,0 +1,10 @@
1
+ 'use client';
2
+ import { useContext } from 'react';
3
+ import { Q as QuilttSettings } from './QuilttSettings-client-BK-0SQME.js';
4
+
5
+ const useQuilttSettings = ()=>{
6
+ const settings = useContext(QuilttSettings);
7
+ return settings;
8
+ };
9
+
10
+ export { useQuilttSettings as u };
@@ -0,0 +1,69 @@
1
+ 'use client';
2
+ import { useMemo, useEffect, useCallback } from 'react';
3
+ import { Timeoutable, JsonWebTokenParse } from '@quiltt/core';
4
+ import { u as useStorage } from './useStorage-client-DHcq3Kuh.js';
5
+
6
+ // Initialize JWT parser with our specific claims type
7
+ const parse = JsonWebTokenParse;
8
+ // Global timer to manage token expiration across all hook instances
9
+ const sessionTimer = new Timeoutable();
10
+ /**
11
+ * Custom hook to manage JWT session state with automatic expiration handling.
12
+ * Provides global singleton access to the session across components and windows.
13
+ *
14
+ * TODO: Support Rotation before Expiry
15
+ *
16
+ * Handles two types of data flow:
17
+ * 1. Bottom-up (Login): Token passed through setSession callback
18
+ * 2. Top-down (Refresh): State reinitialized on page reload
19
+ *
20
+ * @param storageKey - Key used for storing session in useStorage (defaults to 'session')
21
+ * @returns [session, setSession] - Current session state and setter function
22
+ */ const useSession = (storageKey = 'session')=>{
23
+ const [token, setToken] = useStorage(storageKey);
24
+ // Parse token into session data, updates when token changes
25
+ const session = useMemo(()=>parse(token), [
26
+ token
27
+ ]);
28
+ // Handle session expiration
29
+ useEffect(()=>{
30
+ if (!session) return;
31
+ const expirationMS = session.claims.exp * 1000;
32
+ const expire = ()=>setToken(null);
33
+ // Clear immediately if already expired
34
+ if (Date.now() >= expirationMS) {
35
+ expire();
36
+ } else {
37
+ // Set timer to clear session at expiration time
38
+ sessionTimer.set(expire, expirationMS - Date.now());
39
+ return ()=>sessionTimer.clear(expire);
40
+ }
41
+ }, [
42
+ session,
43
+ setToken
44
+ ]);
45
+ /**
46
+ * Validates and updates the session token.
47
+ * - Handles both direct values and updater functions
48
+ * - Validates new tokens before setting them
49
+ * - Prevents unnecessary updates for same token value
50
+ * - Allows clearing the session with null/undefined
51
+ */ const setSession = useCallback((nextState)=>{
52
+ const newState = nextState instanceof Function ? nextState(token) : nextState;
53
+ // Only update if:
54
+ // 1. The token has actually changed AND
55
+ // 2. Either the new state is falsy (clearing session) OR it's a valid token
56
+ if (token !== newState && (!newState || parse(newState))) {
57
+ setToken(newState);
58
+ }
59
+ }, [
60
+ token,
61
+ setToken
62
+ ]);
63
+ return [
64
+ session,
65
+ setSession
66
+ ];
67
+ };
68
+
69
+ export { useSession as u };
@@ -21,9 +21,8 @@ import { GlobalStorage } from '@quiltt/core';
21
21
  * @returns {Array} [storage, setStorage]
22
22
  */ const useStorage = (key, initialState)=>{
23
23
  const getStorage = useCallback(()=>{
24
- let state;
25
- // biome-ignore lint/suspicious/noAssignInExpressions: <explanation>
26
- if ((state = GlobalStorage.get(key)) !== undefined) {
24
+ const state = GlobalStorage.get(key);
25
+ if (state !== undefined) {
27
26
  return state;
28
27
  }
29
28
  return initialState;
@@ -36,23 +35,33 @@ import { GlobalStorage } from '@quiltt/core';
36
35
  const newState = nextState instanceof Function ? nextState(hookState) : nextState;
37
36
  if (hookState !== newState) {
38
37
  GlobalStorage.set(key, newState);
38
+ // Immediately update hook state as well
39
+ setHookState(newState);
39
40
  }
40
41
  }, [
41
42
  key,
42
43
  hookState
43
44
  ]);
44
- /**
45
- * The empty dependency array ensures that the effect runs only once when the component mounts
46
- * and doesn't re-run unnecessarily on subsequent renders because it doesn't depend on any
47
- * props or state variables that could change during the component's lifetime.
48
- *
49
- * Use an empty dependency array to avoid unnecessary re-renders.
50
- */ // biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
51
45
  useEffect(()=>{
52
- GlobalStorage.subscribe(key, setHookState);
53
- setHookState(getStorage());
46
+ // Subscribe to storage changes and ensure state is synchronized
47
+ // Reruns when key or state changes to maintain consistency
48
+ GlobalStorage.subscribe(key, (newValue)=>{
49
+ // Only update if the value is different from current state
50
+ if (newValue !== hookState) {
51
+ setHookState(newValue);
52
+ }
53
+ });
54
+ // Initial sync
55
+ const initialValue = getStorage();
56
+ if (initialValue !== hookState) {
57
+ setHookState(initialValue);
58
+ }
54
59
  return ()=>GlobalStorage.unsubscribe(key, setHookState);
55
- }, []);
60
+ }, [
61
+ key,
62
+ hookState,
63
+ getStorage
64
+ ]);
56
65
  return [
57
66
  hookState,
58
67
  setStorage
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quiltt/react",
3
- "version": "3.9.3",
3
+ "version": "3.9.5",
4
4
  "description": "React Components and Hooks for Quiltt Connector",
5
5
  "keywords": [
6
6
  "quiltt",
@@ -35,14 +35,14 @@
35
35
  "main": "dist/index.js",
36
36
  "dependencies": {
37
37
  "@apollo/client": "^3.12.4",
38
- "@quiltt/core": "3.9.3"
38
+ "@quiltt/core": "3.9.5"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@biomejs/biome": "1.9.4",
42
- "@types/node": "22.10.5",
42
+ "@types/node": "22.13.2",
43
43
  "@types/react": "18.3.12",
44
44
  "@types/react-dom": "18.3.1",
45
- "bunchee": "6.2.0",
45
+ "bunchee": "6.3.4",
46
46
  "react": "18.3.1",
47
47
  "react-dom": "18.3.1",
48
48
  "rimraf": "6.0.1",
@@ -2,8 +2,8 @@ import type { ElementType, MouseEvent, PropsWithChildren } from 'react'
2
2
 
3
3
  import type { ConnectorSDKCallbacks } from '@quiltt/core'
4
4
 
5
- import { useQuilttConnector } from '../hooks/useQuilttConnector'
6
- import type { PropsOf } from '../types'
5
+ import { useQuilttConnector } from '@/hooks/useQuilttConnector'
6
+ import type { PropsOf } from '@/types'
7
7
 
8
8
  // Base button props without callback-specific properties
9
9
  type BaseQuilttButtonProps<T extends ElementType> = {
@@ -2,8 +2,8 @@ import type { ElementType, PropsWithChildren } from 'react'
2
2
 
3
3
  import type { ConnectorSDKCallbacks } from '@quiltt/core'
4
4
 
5
- import { useQuilttConnector } from '../hooks/useQuilttConnector'
6
- import type { PropsOf } from '../types'
5
+ import { useQuilttConnector } from '@/hooks/useQuilttConnector'
6
+ import type { PropsOf } from '@/types'
7
7
 
8
8
  type QuilttContainerProps<T extends ElementType> = PropsWithChildren<
9
9
  {
@@ -0,0 +1,9 @@
1
+ 'use client'
2
+
3
+ import { createContext } from 'react'
4
+
5
+ type QuilttSettingsContext = {
6
+ clientId?: string
7
+ }
8
+
9
+ export const QuilttSettings = createContext<QuilttSettingsContext>({})
@@ -0,0 +1 @@
1
+ export * from './QuilttSettings'
@@ -8,7 +8,7 @@ import type {
8
8
  UnprocessableResponse,
9
9
  } from '@quiltt/core'
10
10
 
11
- import type { SetSession } from '..'
11
+ import type { SetSession } from '@/hooks/useSession'
12
12
 
13
13
  type AuthenticateSessionCallbacks = {
14
14
  onSuccess?: () => unknown
@@ -8,7 +8,7 @@ import type {
8
8
  UsernamePayload,
9
9
  } from '@quiltt/core'
10
10
 
11
- import type { SetSession } from '../useSession'
11
+ import type { SetSession } from '@/hooks/useSession'
12
12
 
13
13
  type IdentifySessionCallbacks = {
14
14
  onSuccess?: () => unknown
@@ -4,7 +4,7 @@ import type { AuthAPI, Maybe, QuilttJWT } from '@quiltt/core'
4
4
  import { JsonWebTokenParse } from '@quiltt/core'
5
5
  import type { PrivateClaims } from '@quiltt/core'
6
6
 
7
- import type { SetSession } from '../useSession'
7
+ import type { SetSession } from '@/hooks/useSession'
8
8
 
9
9
  export type ImportSession = (token: string) => Promise<boolean>
10
10
 
@@ -2,7 +2,7 @@ import { useCallback } from 'react'
2
2
 
3
3
  import type { AuthAPI, Maybe, QuilttJWT } from '@quiltt/core'
4
4
 
5
- import type { SetSession } from '../useSession'
5
+ import type { SetSession } from '@/hooks/useSession'
6
6
 
7
7
  export type RevokeSession = () => Promise<void>
8
8
 
@@ -3,16 +3,15 @@
3
3
  import { useCallback, useEffect, useState } from 'react'
4
4
 
5
5
  import { cdnBase } from '@quiltt/core'
6
-
7
6
  import type {
8
7
  ConnectorSDK,
9
8
  ConnectorSDKConnector,
10
9
  ConnectorSDKConnectorOptions,
11
10
  } from '@quiltt/core'
12
11
 
13
- import { version } from '../version'
14
- import { useQuilttSession } from './useQuilttSession'
15
- import { useScript } from './useScript'
12
+ import { useQuilttSession } from '@/hooks/useQuilttSession'
13
+ import { useScript } from '@/hooks/useScript'
14
+ import { version } from '@/version'
16
15
 
17
16
  declare const Quiltt: ConnectorSDK
18
17
 
@@ -1,17 +1,13 @@
1
1
  'use client'
2
2
 
3
- import { createContext, useContext } from 'react'
3
+ import { useContext } from 'react'
4
4
 
5
- type QuilttSettingsContext = {
6
- clientId?: string | undefined
7
- }
8
-
9
- export const QuilttSettings = createContext<QuilttSettingsContext>({})
5
+ import { QuilttSettings } from '@/contexts/QuilttSettings'
10
6
 
11
7
  export const useQuilttSettings = () => {
12
8
  const settings = useContext(QuilttSettings)
13
9
 
14
- return { ...settings }
10
+ return settings
15
11
  }
16
12
 
17
13
  export default useQuilttSettings
@@ -10,52 +10,62 @@ import { useStorage } from './useStorage'
10
10
 
11
11
  export type SetSession = Dispatch<SetStateAction<Maybe<string> | undefined>>
12
12
 
13
+ // Initialize JWT parser with our specific claims type
13
14
  const parse = JsonWebTokenParse<PrivateClaims>
14
15
 
15
- /**
16
- * Singleton timeout, allows hooks to come and go, while ensuring that there is
17
- * one notification being sent, preventing race conditions.
18
- */
16
+ // Global timer to manage token expiration across all hook instances
19
17
  const sessionTimer = new Timeoutable()
20
18
 
21
19
  /**
22
- * useSession uses useStorage to support a global singleton style of access. When
23
- * updated, all components, and windows should also invalidate.
20
+ * Custom hook to manage JWT session state with automatic expiration handling.
21
+ * Provides global singleton access to the session across components and windows.
24
22
  *
25
23
  * TODO: Support Rotation before Expiry
26
24
  *
27
- * Dataflow can come from two directions:
28
- * 1. Login - Bottom Up
29
- * This happens on login, when a token is passed up through the setSession
30
- * callback. From here it needs to be stored, and shared for usage.
31
- * 2. Refresh - Top Down
32
- * This happens when a page is reloaded or a person returns, and everything is
33
- * reinitialized.
25
+ * Handles two types of data flow:
26
+ * 1. Bottom-up (Login): Token passed through setSession callback
27
+ * 2. Top-down (Refresh): State reinitialized on page reload
28
+ *
29
+ * @param storageKey - Key used for storing session in useStorage (defaults to 'session')
30
+ * @returns [session, setSession] - Current session state and setter function
34
31
  */
35
32
  export const useSession = (storageKey = 'session'): [Maybe<QuilttJWT> | undefined, SetSession] => {
36
33
  const [token, setToken] = useStorage<string>(storageKey)
34
+
35
+ // Parse token into session data, updates when token changes
37
36
  const session = useMemo(() => parse(token), [token])
38
37
 
39
- // Clear session if/when it expires
38
+ // Handle session expiration
40
39
  useEffect(() => {
41
40
  if (!session) return
42
41
 
43
42
  const expirationMS = session.claims.exp * 1000
44
43
  const expire = () => setToken(null)
45
44
 
45
+ // Clear immediately if already expired
46
46
  if (Date.now() >= expirationMS) {
47
47
  expire()
48
48
  } else {
49
+ // Set timer to clear session at expiration time
49
50
  sessionTimer.set(expire, expirationMS - Date.now())
50
51
  return () => sessionTimer.clear(expire)
51
52
  }
52
53
  }, [session, setToken])
53
54
 
54
- // Bubbles up from Login
55
+ /**
56
+ * Validates and updates the session token.
57
+ * - Handles both direct values and updater functions
58
+ * - Validates new tokens before setting them
59
+ * - Prevents unnecessary updates for same token value
60
+ * - Allows clearing the session with null/undefined
61
+ */
55
62
  const setSession = useCallback(
56
63
  (nextState: Maybe<string> | SetStateAction<Maybe<string> | undefined> | undefined) => {
57
64
  const newState = nextState instanceof Function ? nextState(token) : nextState
58
65
 
66
+ // Only update if:
67
+ // 1. The token has actually changed AND
68
+ // 2. Either the new state is falsy (clearing session) OR it's a valid token
59
69
  if (token !== newState && (!newState || parse(newState))) {
60
70
  setToken(newState)
61
71
  }
@@ -29,10 +29,8 @@ export const useStorage = <T>(
29
29
  initialState?: Maybe<T>
30
30
  ): [Maybe<T> | undefined, Dispatch<SetStateAction<Maybe<T> | undefined>>] => {
31
31
  const getStorage = useCallback(() => {
32
- let state: Maybe<T>
33
-
34
- // biome-ignore lint/suspicious/noAssignInExpressions: <explanation>
35
- if ((state = GlobalStorage.get(key)) !== undefined) {
32
+ const state = GlobalStorage.get(key)
33
+ if (state !== undefined) {
36
34
  return state
37
35
  }
38
36
 
@@ -44,29 +42,33 @@ export const useStorage = <T>(
44
42
  const setStorage = useCallback(
45
43
  (nextState: Maybe<T> | SetStateAction<Maybe<T> | undefined>) => {
46
44
  const newState = nextState instanceof Function ? nextState(hookState) : nextState
47
-
48
45
  if (hookState !== newState) {
49
46
  GlobalStorage.set(key, newState)
47
+ // Immediately update hook state as well
48
+ setHookState(newState)
50
49
  }
51
50
  },
52
51
  [key, hookState]
53
52
  )
54
53
 
55
- /**
56
- * The empty dependency array ensures that the effect runs only once when the component mounts
57
- * and doesn't re-run unnecessarily on subsequent renders because it doesn't depend on any
58
- * props or state variables that could change during the component's lifetime.
59
- *
60
- * Use an empty dependency array to avoid unnecessary re-renders.
61
- */
62
- // biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
63
54
  useEffect(() => {
64
- GlobalStorage.subscribe(key, setHookState)
55
+ // Subscribe to storage changes and ensure state is synchronized
56
+ // Reruns when key or state changes to maintain consistency
57
+ GlobalStorage.subscribe(key, (newValue) => {
58
+ // Only update if the value is different from current state
59
+ if (newValue !== hookState) {
60
+ setHookState(newValue)
61
+ }
62
+ })
65
63
 
66
- setHookState(getStorage())
64
+ // Initial sync
65
+ const initialValue = getStorage()
66
+ if (initialValue !== hookState) {
67
+ setHookState(initialValue)
68
+ }
67
69
 
68
70
  return () => GlobalStorage.unsubscribe(key, setHookState)
69
- }, [])
71
+ }, [key, hookState, getStorage])
70
72
 
71
73
  return [hookState, setStorage]
72
74
  }
@@ -3,7 +3,7 @@
3
3
  import type { FC, PropsWithChildren } from 'react'
4
4
  import { useState } from 'react'
5
5
 
6
- import { QuilttSettings } from '../hooks'
6
+ import { QuilttSettings } from '@/contexts/QuilttSettings'
7
7
 
8
8
  type QuilttSettingsProviderProps = PropsWithChildren & {
9
9
  /** The Client ID to use for the client-side Auth API */
@@ -1,12 +0,0 @@
1
- 'use client';
2
- import { createContext, useContext } from 'react';
3
-
4
- const QuilttSettings = createContext({});
5
- const useQuilttSettings = ()=>{
6
- const settings = useContext(QuilttSettings);
7
- return {
8
- ...settings
9
- };
10
- };
11
-
12
- export { QuilttSettings as Q, useQuilttSettings as u };
@@ -1,60 +0,0 @@
1
- 'use client';
2
- import { useMemo, useEffect, useCallback } from 'react';
3
- import { Timeoutable, JsonWebTokenParse } from '@quiltt/core';
4
- import { u as useStorage } from './useStorage-client-B3keU-oI.js';
5
-
6
- const parse = JsonWebTokenParse;
7
- /**
8
- * Singleton timeout, allows hooks to come and go, while ensuring that there is
9
- * one notification being sent, preventing race conditions.
10
- */ const sessionTimer = new Timeoutable();
11
- /**
12
- * useSession uses useStorage to support a global singleton style of access. When
13
- * updated, all components, and windows should also invalidate.
14
- *
15
- * TODO: Support Rotation before Expiry
16
- *
17
- * Dataflow can come from two directions:
18
- * 1. Login - Bottom Up
19
- * This happens on login, when a token is passed up through the setSession
20
- * callback. From here it needs to be stored, and shared for usage.
21
- * 2. Refresh - Top Down
22
- * This happens when a page is reloaded or a person returns, and everything is
23
- * reinitialized.
24
- */ const useSession = (storageKey = 'session')=>{
25
- const [token, setToken] = useStorage(storageKey);
26
- const session = useMemo(()=>parse(token), [
27
- token
28
- ]);
29
- // Clear session if/when it expires
30
- useEffect(()=>{
31
- if (!session) return;
32
- const expirationMS = session.claims.exp * 1000;
33
- const expire = ()=>setToken(null);
34
- if (Date.now() >= expirationMS) {
35
- expire();
36
- } else {
37
- sessionTimer.set(expire, expirationMS - Date.now());
38
- return ()=>sessionTimer.clear(expire);
39
- }
40
- }, [
41
- session,
42
- setToken
43
- ]);
44
- // Bubbles up from Login
45
- const setSession = useCallback((nextState)=>{
46
- const newState = nextState instanceof Function ? nextState(token) : nextState;
47
- if (token !== newState && (!newState || parse(newState))) {
48
- setToken(newState);
49
- }
50
- }, [
51
- token,
52
- setToken
53
- ]);
54
- return [
55
- session,
56
- setSession
57
- ];
58
- };
59
-
60
- export { useSession as u };