@quiltt/react 3.9.4 → 3.9.6
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 +18 -0
- package/dist/{QuilttAuthProvider-client-D6Iok6TS.js → QuilttAuthProvider-client-CER-TOln.js} +3 -3
- package/dist/QuilttSettings-client-BK-0SQME.js +6 -0
- package/dist/{QuilttSettingsProvider-client-EfyHgg4i.js → QuilttSettingsProvider-client-Va7uJ_dQ.js} +1 -4
- package/dist/index.d.ts +10 -16
- package/dist/index.js +8 -8
- package/dist/{useQuilttConnector-client-CDNsqITk.js → useQuilttConnector-client-BPHMnEAn.js} +2 -2
- package/dist/{useQuilttSession-client-KZRjdl5I.js → useQuilttSession-client-D2mjVT4S.js} +3 -3
- package/dist/useQuilttSettings-client-BOCBjFXe.js +10 -0
- package/dist/useSession-client-CCAvnROP.js +69 -0
- package/dist/{useStorage-client-B3keU-oI.js → useStorage-client-DHcq3Kuh.js} +22 -13
- package/package.json +4 -4
- package/src/components/QuilttButton.tsx +2 -2
- package/src/components/QuilttContainer.tsx +2 -2
- package/src/contexts/QuilttSettings.ts +9 -0
- package/src/contexts/index.ts +1 -0
- package/src/hooks/session/useAuthenticateSession.ts +1 -1
- package/src/hooks/session/useIdentifySession.ts +1 -1
- package/src/hooks/session/useImportSession.ts +1 -1
- package/src/hooks/session/useRevokeSession.ts +1 -1
- package/src/hooks/useQuilttConnector.ts +3 -4
- package/src/hooks/useQuilttSettings.ts +3 -7
- package/src/hooks/useSession.ts +25 -15
- package/src/hooks/useStorage.ts +18 -16
- package/src/providers/QuilttSettingsProvider.tsx +1 -1
- package/dist/useQuilttSettings-client-DU_Qfc8X.js +0 -12
- 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.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#328](https://github.com/quiltt/quiltt-js/pull/328) [`6b8751c`](https://github.com/quiltt/quiltt-js/commit/6b8751c981e9e74b347227bc9f427585d21870cd) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Updated QuilttConnector OAuth handler
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [[`6b8751c`](https://github.com/quiltt/quiltt-js/commit/6b8751c981e9e74b347227bc9f427585d21870cd)]:
|
|
10
|
+
- @quiltt/core@3.9.6
|
|
11
|
+
|
|
12
|
+
## 3.9.5
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- [#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
|
|
17
|
+
|
|
18
|
+
- Updated dependencies [[`62b7323`](https://github.com/quiltt/quiltt-js/commit/62b732371a8d57242170e0ae838baa4ca8e78059)]:
|
|
19
|
+
- @quiltt/core@3.9.5
|
|
20
|
+
|
|
3
21
|
## 3.9.4
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/dist/{QuilttAuthProvider-client-D6Iok6TS.js → QuilttAuthProvider-client-CER-TOln.js}
RENAMED
|
@@ -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 './
|
|
6
|
-
import './useSession-client-
|
|
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-
|
|
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)=>{
|
package/dist/{QuilttSettingsProvider-client-EfyHgg4i.js → QuilttSettingsProvider-client-Va7uJ_dQ.js}
RENAMED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx } from 'react/jsx-runtime';
|
|
3
3
|
import { useState } from 'react';
|
|
4
|
-
import '
|
|
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
|
-
*
|
|
22
|
-
*
|
|
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
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
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
|
|
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,
|
|
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-
|
|
5
|
-
export { b as useAuthenticateSession, a as useIdentifySession, u as useImportSession, c as useRevokeSession } from './QuilttAuthProvider-client-
|
|
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-
|
|
8
|
-
export { u as useQuilttSession } from './useQuilttSession-client-
|
|
9
|
-
export {
|
|
10
|
-
export { u as useSession } from './useSession-client-
|
|
11
|
-
export { u as useStorage } from './useStorage-client-
|
|
7
|
+
import { u as useQuilttConnector } from './useQuilttConnector-client-BPHMnEAn.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-
|
|
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, {
|
package/dist/{useQuilttConnector-client-CDNsqITk.js → useQuilttConnector-client-BPHMnEAn.js}
RENAMED
|
@@ -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-
|
|
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.
|
|
7
|
+
var version = "3.9.6";
|
|
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-
|
|
5
|
-
import { u as useQuilttSettings } from './useQuilttSettings-client-
|
|
6
|
-
import { u as useSession } from './useSession-client-
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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
|
+
"version": "3.9.6",
|
|
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.
|
|
38
|
+
"@quiltt/core": "3.9.6"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@biomejs/biome": "1.9.4",
|
|
42
|
-
"@types/node": "22.10
|
|
42
|
+
"@types/node": "22.13.10",
|
|
43
43
|
"@types/react": "18.3.12",
|
|
44
44
|
"@types/react-dom": "18.3.1",
|
|
45
|
-
"bunchee": "6.
|
|
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 '
|
|
6
|
-
import type { PropsOf } from '
|
|
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 '
|
|
6
|
-
import type { PropsOf } from '
|
|
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 @@
|
|
|
1
|
+
export * from './QuilttSettings'
|
|
@@ -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 '
|
|
7
|
+
import type { SetSession } from '@/hooks/useSession'
|
|
8
8
|
|
|
9
9
|
export type ImportSession = (token: string) => Promise<boolean>
|
|
10
10
|
|
|
@@ -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 {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
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 {
|
|
3
|
+
import { useContext } from 'react'
|
|
4
4
|
|
|
5
|
-
|
|
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
|
|
10
|
+
return settings
|
|
15
11
|
}
|
|
16
12
|
|
|
17
13
|
export default useQuilttSettings
|
package/src/hooks/useSession.ts
CHANGED
|
@@ -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
|
-
*
|
|
23
|
-
*
|
|
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
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
}
|
package/src/hooks/useStorage.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 '
|
|
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 };
|