@quiltt/vue 5.1.2
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/LICENSE.md +9 -0
- package/README.md +212 -0
- package/dist/components/index.cjs +707 -0
- package/dist/components/index.d.ts +278 -0
- package/dist/components/index.js +703 -0
- package/dist/composables/index.cjs +617 -0
- package/dist/composables/index.d.ts +191 -0
- package/dist/composables/index.js +609 -0
- package/dist/index.cjs +75 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/plugin/index.cjs +176 -0
- package/dist/plugin/index.d.ts +48 -0
- package/dist/plugin/index.js +171 -0
- package/package.json +81 -0
- package/src/components/QuilttButton.ts +121 -0
- package/src/components/QuilttConnector.ts +215 -0
- package/src/components/QuilttContainer.ts +130 -0
- package/src/components/index.ts +3 -0
- package/src/composables/index.ts +7 -0
- package/src/composables/useQuilttConnector.ts +312 -0
- package/src/composables/useQuilttInstitutions.ts +114 -0
- package/src/composables/useQuilttResolvable.ts +94 -0
- package/src/composables/useQuilttSession.ts +239 -0
- package/src/composables/useQuilttSettings.ts +15 -0
- package/src/composables/useSession.ts +74 -0
- package/src/composables/useStorage.ts +47 -0
- package/src/constants/deprecation-warnings.ts +2 -0
- package/src/index.ts +34 -0
- package/src/plugin/QuilttPlugin.ts +204 -0
- package/src/plugin/index.ts +23 -0
- package/src/plugin/keys.ts +26 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/telemetry.ts +73 -0
- package/src/version.ts +1 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { computed, onUnmounted, ref, shallowRef, watch } from 'vue'
|
|
2
|
+
|
|
3
|
+
import type { ErrorData, InstitutionsData } from '@quiltt/core'
|
|
4
|
+
import { ConnectorsAPI } from '@quiltt/core'
|
|
5
|
+
import { extractVersionNumber } from '@quiltt/core/utils'
|
|
6
|
+
|
|
7
|
+
import { getSDKAgent } from '../utils'
|
|
8
|
+
import { version } from '../version'
|
|
9
|
+
import { useQuilttSession } from './useQuilttSession'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Search institutions for a connector.
|
|
13
|
+
* Requires QuilttPlugin session context and throws when used without it.
|
|
14
|
+
*/
|
|
15
|
+
export const useQuilttInstitutions = (
|
|
16
|
+
connectorId: string,
|
|
17
|
+
onErrorCallback?: (msg: string) => void
|
|
18
|
+
) => {
|
|
19
|
+
const { session } = useQuilttSession()
|
|
20
|
+
|
|
21
|
+
const searchTermInput = ref('')
|
|
22
|
+
const searchTerm = ref('')
|
|
23
|
+
const searchResults = ref<InstitutionsData>([])
|
|
24
|
+
const isSearching = ref(false)
|
|
25
|
+
|
|
26
|
+
const debounceTimer = ref<ReturnType<typeof setTimeout> | undefined>()
|
|
27
|
+
const abortController = shallowRef<AbortController | undefined>()
|
|
28
|
+
|
|
29
|
+
const sdkVersion = extractVersionNumber(version)
|
|
30
|
+
const connectorsAPI = new ConnectorsAPI(connectorId, getSDKAgent(sdkVersion))
|
|
31
|
+
|
|
32
|
+
const handleError = (message: string) => {
|
|
33
|
+
const errorMessage = message || 'Unknown error occurred while searching institutions'
|
|
34
|
+
console.error('Quiltt Institutions Search Error:', errorMessage)
|
|
35
|
+
onErrorCallback?.(errorMessage)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const setSearchTerm = (term: string) => {
|
|
39
|
+
searchTermInput.value = term
|
|
40
|
+
|
|
41
|
+
if (debounceTimer.value) {
|
|
42
|
+
clearTimeout(debounceTimer.value)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (term.trim().length < 2) {
|
|
46
|
+
abortController.value?.abort()
|
|
47
|
+
abortController.value = undefined
|
|
48
|
+
searchTerm.value = ''
|
|
49
|
+
searchResults.value = []
|
|
50
|
+
isSearching.value = false
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
isSearching.value = true
|
|
55
|
+
debounceTimer.value = setTimeout(() => {
|
|
56
|
+
searchTerm.value = term
|
|
57
|
+
}, 350)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
watch(
|
|
61
|
+
[() => session.value?.token, searchTerm],
|
|
62
|
+
async ([token, term]) => {
|
|
63
|
+
if (!token || !connectorId || !term || term.trim().length < 2) {
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
abortController.value?.abort()
|
|
68
|
+
const controller = new AbortController()
|
|
69
|
+
abortController.value = controller
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const response = await connectorsAPI.searchInstitutions(
|
|
73
|
+
token,
|
|
74
|
+
connectorId,
|
|
75
|
+
term,
|
|
76
|
+
controller.signal
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if (abortController.value !== controller || controller.signal.aborted) {
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (response.status === 200) {
|
|
84
|
+
searchResults.value = response.data as InstitutionsData
|
|
85
|
+
} else {
|
|
86
|
+
handleError((response.data as ErrorData).message || 'Failed to fetch institutions')
|
|
87
|
+
}
|
|
88
|
+
} catch (error: any) {
|
|
89
|
+
if (abortController.value === controller && !controller.signal.aborted) {
|
|
90
|
+
handleError(error?.message)
|
|
91
|
+
}
|
|
92
|
+
} finally {
|
|
93
|
+
if (abortController.value === controller && !controller.signal.aborted) {
|
|
94
|
+
isSearching.value = false
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{ immediate: false }
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
onUnmounted(() => {
|
|
102
|
+
if (debounceTimer.value) {
|
|
103
|
+
clearTimeout(debounceTimer.value)
|
|
104
|
+
}
|
|
105
|
+
abortController.value?.abort()
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
searchTerm: computed(() => searchTerm.value),
|
|
110
|
+
searchResults: computed(() => searchResults.value),
|
|
111
|
+
isSearching: computed(() => isSearching.value),
|
|
112
|
+
setSearchTerm,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { computed, ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
import type { ErrorData, ResolvableData } from '@quiltt/core'
|
|
4
|
+
import { ConnectorsAPI } from '@quiltt/core'
|
|
5
|
+
import { extractVersionNumber } from '@quiltt/core/utils'
|
|
6
|
+
|
|
7
|
+
import { getSDKAgent } from '../utils'
|
|
8
|
+
import { version } from '../version'
|
|
9
|
+
import { useQuilttSession } from './useQuilttSession'
|
|
10
|
+
|
|
11
|
+
type ProviderId = {
|
|
12
|
+
plaid?: string
|
|
13
|
+
mock?: string
|
|
14
|
+
mx?: string
|
|
15
|
+
finicity?: string
|
|
16
|
+
akoya?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Check whether a provider link is resolvable for a connector.
|
|
21
|
+
* Requires QuilttPlugin session context and throws when used without it.
|
|
22
|
+
*/
|
|
23
|
+
export const useQuilttResolvable = (
|
|
24
|
+
connectorId: string,
|
|
25
|
+
onErrorCallback?: (msg: string) => void
|
|
26
|
+
) => {
|
|
27
|
+
const { session } = useQuilttSession()
|
|
28
|
+
const sdkVersion = extractVersionNumber(version)
|
|
29
|
+
const connectorsAPI = new ConnectorsAPI(connectorId, getSDKAgent(sdkVersion))
|
|
30
|
+
|
|
31
|
+
const isLoading = ref(false)
|
|
32
|
+
const isResolvable = ref<boolean | null>(null)
|
|
33
|
+
const error = ref<string | null>(null)
|
|
34
|
+
|
|
35
|
+
const handleError = (message: string) => {
|
|
36
|
+
const errorMessage = message || 'Unknown error occurred while checking resolvability'
|
|
37
|
+
|
|
38
|
+
error.value = errorMessage
|
|
39
|
+
console.error('Quiltt Connector Resolvable Error:', errorMessage)
|
|
40
|
+
onErrorCallback?.(errorMessage)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const checkResolvable = async (providerId: ProviderId): Promise<boolean | null> => {
|
|
44
|
+
if (!session.value?.token) {
|
|
45
|
+
handleError('Missing session token')
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!connectorId) {
|
|
50
|
+
handleError('Missing connector ID')
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const hasProviderId = Object.values(providerId).some((id) => !!id)
|
|
55
|
+
if (!hasProviderId) {
|
|
56
|
+
handleError('No provider ID specified')
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
isLoading.value = true
|
|
61
|
+
error.value = null
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const response = await connectorsAPI.checkResolvable(
|
|
65
|
+
session.value.token,
|
|
66
|
+
connectorId,
|
|
67
|
+
providerId
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if (response.status === 200) {
|
|
71
|
+
const result = (response.data as ResolvableData).resolvable
|
|
72
|
+
isResolvable.value = result
|
|
73
|
+
return result
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
handleError((response.data as ErrorData).message || 'Failed to check resolvability')
|
|
77
|
+
isResolvable.value = null
|
|
78
|
+
return null
|
|
79
|
+
} catch (caught: any) {
|
|
80
|
+
handleError(caught?.message)
|
|
81
|
+
isResolvable.value = null
|
|
82
|
+
return null
|
|
83
|
+
} finally {
|
|
84
|
+
isLoading.value = false
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
checkResolvable,
|
|
90
|
+
isLoading: computed(() => isLoading.value),
|
|
91
|
+
isResolvable: computed(() => isResolvable.value),
|
|
92
|
+
error: computed(() => error.value),
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quiltt Session Composable
|
|
3
|
+
*
|
|
4
|
+
* Provides session management functionality for Vue 3 applications.
|
|
5
|
+
* Must be used within a component tree where QuilttPlugin is installed.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```vue
|
|
9
|
+
* <script setup>
|
|
10
|
+
* import { useQuilttSession } from '@quiltt/vue'
|
|
11
|
+
*
|
|
12
|
+
* const { session, importSession, revokeSession } = useQuilttSession()
|
|
13
|
+
*
|
|
14
|
+
* // Import a session token
|
|
15
|
+
* await importSession('<SESSION_TOKEN>')
|
|
16
|
+
*
|
|
17
|
+
* // Access session data
|
|
18
|
+
* console.log(session.value?.token)
|
|
19
|
+
*
|
|
20
|
+
* // Revoke the session
|
|
21
|
+
* await revokeSession()
|
|
22
|
+
* </script>
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { type ComputedRef, computed, inject } from 'vue'
|
|
27
|
+
|
|
28
|
+
import type {
|
|
29
|
+
Maybe,
|
|
30
|
+
PasscodePayload,
|
|
31
|
+
PrivateClaims,
|
|
32
|
+
QuilttJWT,
|
|
33
|
+
SessionResponse,
|
|
34
|
+
UnprocessableData,
|
|
35
|
+
UnprocessableResponse,
|
|
36
|
+
UsernamePayload,
|
|
37
|
+
} from '@quiltt/core'
|
|
38
|
+
import { AuthAPI, JsonWebTokenParse } from '@quiltt/core'
|
|
39
|
+
|
|
40
|
+
import { QuilttClientIdKey, QuilttSessionKey, QuilttSetSessionKey } from '../plugin/keys'
|
|
41
|
+
|
|
42
|
+
// Initialize JWT parser
|
|
43
|
+
const parse = JsonWebTokenParse<PrivateClaims>
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Callbacks for identify session operation
|
|
47
|
+
*/
|
|
48
|
+
export interface IdentifySessionCallbacks {
|
|
49
|
+
onSuccess?: () => unknown
|
|
50
|
+
onChallenged?: () => unknown
|
|
51
|
+
onError?: (errors: UnprocessableData) => unknown
|
|
52
|
+
onForbidden?: () => unknown
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Callbacks for authenticate session operation
|
|
57
|
+
*/
|
|
58
|
+
export interface AuthenticateSessionCallbacks {
|
|
59
|
+
onSuccess?: () => unknown
|
|
60
|
+
onFailure?: () => unknown
|
|
61
|
+
onError?: (errors: UnprocessableData) => unknown
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type ImportSession = (token: string, environmentId?: string) => Promise<boolean>
|
|
65
|
+
export type IdentifySession = (
|
|
66
|
+
payload: UsernamePayload,
|
|
67
|
+
callbacks: IdentifySessionCallbacks
|
|
68
|
+
) => Promise<unknown>
|
|
69
|
+
export type AuthenticateSession = (
|
|
70
|
+
payload: PasscodePayload,
|
|
71
|
+
callbacks: AuthenticateSessionCallbacks
|
|
72
|
+
) => Promise<unknown>
|
|
73
|
+
export type RevokeSession = () => Promise<void>
|
|
74
|
+
export type ForgetSession = (token?: string) => void
|
|
75
|
+
|
|
76
|
+
export interface UseQuilttSessionReturn {
|
|
77
|
+
/** Current session (reactive) */
|
|
78
|
+
session: ComputedRef<Maybe<QuilttJWT> | undefined>
|
|
79
|
+
/** Import an existing session token */
|
|
80
|
+
importSession: ImportSession
|
|
81
|
+
/** Start authentication flow with username/email/phone */
|
|
82
|
+
identifySession: IdentifySession
|
|
83
|
+
/** Complete authentication with passcode */
|
|
84
|
+
authenticateSession: AuthenticateSession
|
|
85
|
+
/** Revoke the current session (invalidates token on server) */
|
|
86
|
+
revokeSession: RevokeSession
|
|
87
|
+
/** Forget the current session locally (without server call) */
|
|
88
|
+
forgetSession: ForgetSession
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Composable for managing Quiltt session state
|
|
93
|
+
*
|
|
94
|
+
* Provides methods for importing, creating, and revoking sessions.
|
|
95
|
+
* Session state is automatically synchronized across components.
|
|
96
|
+
* Requires QuilttPlugin provider context and throws when used without it.
|
|
97
|
+
*/
|
|
98
|
+
export const useQuilttSession = (): UseQuilttSessionReturn => {
|
|
99
|
+
const sessionRef = inject(QuilttSessionKey)
|
|
100
|
+
const setSession = inject(QuilttSetSessionKey)
|
|
101
|
+
const clientIdRef = inject(QuilttClientIdKey)
|
|
102
|
+
|
|
103
|
+
if (!sessionRef || !setSession) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
'[Quiltt] useQuilttSession must be used within a component where QuilttPlugin is installed. ' +
|
|
106
|
+
'Make sure to call app.use(QuilttPlugin) before mounting your app.'
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Create a computed ref for the session
|
|
111
|
+
const session = computed(() => sessionRef.value)
|
|
112
|
+
|
|
113
|
+
// Create AuthAPI instance (memoized based on clientId)
|
|
114
|
+
const getAuth = () => new AuthAPI(clientIdRef?.value)
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Import an existing session token
|
|
118
|
+
* Validates the token and sets it as the current session
|
|
119
|
+
*/
|
|
120
|
+
const importSession: ImportSession = async (token, environmentId) => {
|
|
121
|
+
const auth = getAuth()
|
|
122
|
+
|
|
123
|
+
// Is there a token?
|
|
124
|
+
if (!token) return !!sessionRef.value
|
|
125
|
+
|
|
126
|
+
// Is this token already imported?
|
|
127
|
+
if (sessionRef.value && sessionRef.value.token === token) return true
|
|
128
|
+
|
|
129
|
+
const jwt = parse(token)
|
|
130
|
+
|
|
131
|
+
// Is this token a valid JWT?
|
|
132
|
+
if (!jwt) return false
|
|
133
|
+
|
|
134
|
+
// Is this token within the expected environment?
|
|
135
|
+
if (environmentId && jwt.claims.eid !== environmentId) return false
|
|
136
|
+
|
|
137
|
+
// Is this token active?
|
|
138
|
+
const response = await auth.ping(token)
|
|
139
|
+
switch (response.status) {
|
|
140
|
+
case 200:
|
|
141
|
+
setSession(token)
|
|
142
|
+
return true
|
|
143
|
+
|
|
144
|
+
case 401:
|
|
145
|
+
return false
|
|
146
|
+
|
|
147
|
+
default:
|
|
148
|
+
throw new Error(`AuthAPI.ping: Unexpected response status ${response.status}`)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Start authentication flow with username/email/phone
|
|
154
|
+
* Returns a session token or challenges for passcode
|
|
155
|
+
*/
|
|
156
|
+
const identifySession: IdentifySession = async (payload, callbacks) => {
|
|
157
|
+
const auth = getAuth()
|
|
158
|
+
const response = await auth.identify(payload)
|
|
159
|
+
|
|
160
|
+
switch (response.status) {
|
|
161
|
+
case 201: // Created
|
|
162
|
+
setSession((response as SessionResponse).data.token)
|
|
163
|
+
if (callbacks.onSuccess) return callbacks.onSuccess()
|
|
164
|
+
break
|
|
165
|
+
|
|
166
|
+
case 202: // Accepted (needs passcode)
|
|
167
|
+
if (callbacks.onChallenged) return callbacks.onChallenged()
|
|
168
|
+
break
|
|
169
|
+
|
|
170
|
+
case 403: // Forbidden (signups disabled)
|
|
171
|
+
if (callbacks.onForbidden) return callbacks.onForbidden()
|
|
172
|
+
break
|
|
173
|
+
|
|
174
|
+
case 422: // Unprocessable Content
|
|
175
|
+
if (callbacks.onError) return callbacks.onError((response as UnprocessableResponse).data)
|
|
176
|
+
break
|
|
177
|
+
|
|
178
|
+
default:
|
|
179
|
+
throw new Error(`AuthAPI.identify: Unexpected response status ${response.status}`)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Complete authentication with passcode
|
|
185
|
+
*/
|
|
186
|
+
const authenticateSession: AuthenticateSession = async (payload, callbacks) => {
|
|
187
|
+
const auth = getAuth()
|
|
188
|
+
const response = await auth.authenticate(payload)
|
|
189
|
+
|
|
190
|
+
switch (response.status) {
|
|
191
|
+
case 201:
|
|
192
|
+
setSession((response as SessionResponse).data.token)
|
|
193
|
+
if (callbacks.onSuccess) return callbacks.onSuccess()
|
|
194
|
+
break
|
|
195
|
+
|
|
196
|
+
case 401:
|
|
197
|
+
if (callbacks.onFailure) return callbacks.onFailure()
|
|
198
|
+
break
|
|
199
|
+
|
|
200
|
+
case 422:
|
|
201
|
+
if (callbacks.onError) return callbacks.onError((response as UnprocessableResponse).data)
|
|
202
|
+
break
|
|
203
|
+
|
|
204
|
+
default:
|
|
205
|
+
throw new Error(`AuthAPI.authenticate: Unexpected response status ${response.status}`)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Revoke the current session
|
|
211
|
+
* Invalidates the token on the server and clears local state
|
|
212
|
+
*/
|
|
213
|
+
const revokeSession: RevokeSession = async () => {
|
|
214
|
+
if (!sessionRef.value) return
|
|
215
|
+
|
|
216
|
+
const auth = getAuth()
|
|
217
|
+
await auth.revoke(sessionRef.value.token)
|
|
218
|
+
setSession(null)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Forget the current session locally without server call
|
|
223
|
+
* Optionally pass a specific token to guard against async processes clearing wrong session
|
|
224
|
+
*/
|
|
225
|
+
const forgetSession: ForgetSession = (token) => {
|
|
226
|
+
if (!token || (sessionRef.value && token === sessionRef.value.token)) {
|
|
227
|
+
setSession(null)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
session,
|
|
233
|
+
importSession,
|
|
234
|
+
identifySession,
|
|
235
|
+
authenticateSession,
|
|
236
|
+
revokeSession,
|
|
237
|
+
forgetSession,
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { computed, inject } from 'vue'
|
|
2
|
+
|
|
3
|
+
import { QuilttClientIdKey } from '../plugin/keys'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Read plugin-provided Quiltt settings.
|
|
7
|
+
* When used without QuilttPlugin context, values are undefined.
|
|
8
|
+
*/
|
|
9
|
+
export const useQuilttSettings = () => {
|
|
10
|
+
const clientIdRef = inject(QuilttClientIdKey)
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
clientId: computed(() => clientIdRef?.value),
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { computed, onUnmounted, ref, watch } from 'vue'
|
|
2
|
+
|
|
3
|
+
import type { Maybe, PrivateClaims, QuilttJWT } from '@quiltt/core'
|
|
4
|
+
import { JsonWebTokenParse, Timeoutable } from '@quiltt/core'
|
|
5
|
+
|
|
6
|
+
import { useStorage } from './useStorage'
|
|
7
|
+
|
|
8
|
+
const parse = JsonWebTokenParse<PrivateClaims>
|
|
9
|
+
const sessionTimer = new Timeoutable()
|
|
10
|
+
|
|
11
|
+
export const useSession = (storageKey = 'session') => {
|
|
12
|
+
const { storage: token, setStorage } = useStorage<string>(storageKey)
|
|
13
|
+
const session = ref<Maybe<QuilttJWT> | undefined>(parse(token.value))
|
|
14
|
+
let currentExpire: (() => void) | null = null
|
|
15
|
+
|
|
16
|
+
watch(
|
|
17
|
+
token,
|
|
18
|
+
(newToken) => {
|
|
19
|
+
session.value = parse(newToken)
|
|
20
|
+
},
|
|
21
|
+
{ immediate: true }
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
watch(
|
|
25
|
+
() => session.value,
|
|
26
|
+
(nextSession) => {
|
|
27
|
+
if (currentExpire) {
|
|
28
|
+
sessionTimer.clear(currentExpire)
|
|
29
|
+
currentExpire = null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!nextSession) {
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const expirationMS = nextSession.claims.exp * 1000
|
|
37
|
+
const expire = () => setStorage(null)
|
|
38
|
+
currentExpire = expire
|
|
39
|
+
|
|
40
|
+
if (Date.now() >= expirationMS) {
|
|
41
|
+
expire()
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
sessionTimer.set(expire, expirationMS - Date.now())
|
|
46
|
+
},
|
|
47
|
+
{ immediate: true }
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
const setSession = (
|
|
51
|
+
nextState:
|
|
52
|
+
| Maybe<string>
|
|
53
|
+
| undefined
|
|
54
|
+
| ((prev: Maybe<string> | undefined) => Maybe<string> | undefined)
|
|
55
|
+
) => {
|
|
56
|
+
const resolved = nextState instanceof Function ? nextState(token.value) : nextState
|
|
57
|
+
|
|
58
|
+
if (token.value !== resolved && (!resolved || parse(resolved))) {
|
|
59
|
+
setStorage(resolved)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
onUnmounted(() => {
|
|
64
|
+
if (currentExpire) {
|
|
65
|
+
sessionTimer.clear(currentExpire)
|
|
66
|
+
currentExpire = null
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
session: computed(() => session.value),
|
|
72
|
+
setSession,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { computed, getCurrentScope, onScopeDispose, ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
import type { Maybe } from '@quiltt/core'
|
|
4
|
+
import { GlobalStorage } from '@quiltt/core'
|
|
5
|
+
|
|
6
|
+
export const useStorage = <T>(key: string, initialState?: Maybe<T>) => {
|
|
7
|
+
const readStorage = () => {
|
|
8
|
+
const current = GlobalStorage.get(key)
|
|
9
|
+
if (current !== undefined) {
|
|
10
|
+
return current as Maybe<T>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return initialState
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const state = ref<Maybe<T> | undefined>(readStorage())
|
|
17
|
+
|
|
18
|
+
const setStorage = (
|
|
19
|
+
nextState: Maybe<T> | undefined | ((prev: Maybe<T> | undefined) => Maybe<T> | undefined)
|
|
20
|
+
) => {
|
|
21
|
+
const resolved = nextState instanceof Function ? nextState(state.value) : nextState
|
|
22
|
+
|
|
23
|
+
if (state.value !== resolved) {
|
|
24
|
+
GlobalStorage.set(key, resolved)
|
|
25
|
+
state.value = resolved
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const handleStorageChange = (newValue: Maybe<T> | undefined) => {
|
|
30
|
+
state.value = newValue
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
GlobalStorage.subscribe(key, handleStorageChange)
|
|
34
|
+
|
|
35
|
+
// Use onScopeDispose for cleanup in any effect scope (components, effectScope(), etc.)
|
|
36
|
+
// This safely handles cases where composable is called outside a scope
|
|
37
|
+
if (getCurrentScope()) {
|
|
38
|
+
onScopeDispose(() => {
|
|
39
|
+
GlobalStorage.unsubscribe(key, handleStorageChange)
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
storage: computed(() => state.value),
|
|
45
|
+
setStorage,
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// @quiltt/vue - Vue 3 Composables and Components for Quiltt
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// This package provides Vue 3-specific composables and components for
|
|
5
|
+
// integrating Quiltt's financial data platform into Vue applications.
|
|
6
|
+
// It re-exports all @quiltt/core functionality plus Vue-specific features.
|
|
7
|
+
//
|
|
8
|
+
// Main exports:
|
|
9
|
+
// - All @quiltt/core modules (API clients, auth, config, storage, types)
|
|
10
|
+
// - Vue plugin (QuilttPlugin) for app-wide session management
|
|
11
|
+
// - Vue composables (useQuilttSession, useQuilttConnector)
|
|
12
|
+
// - Vue components (QuilttConnector, QuilttButton, QuilttContainer)
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Quiltt Core - Re-export all modules from @quiltt/core
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Re-export all core Quiltt functionality so users only need to install
|
|
19
|
+
// @quiltt/vue instead of both @quiltt/core and @quiltt/vue.
|
|
20
|
+
export * from '@quiltt/core/api'
|
|
21
|
+
export * from '@quiltt/core/auth'
|
|
22
|
+
export * from '@quiltt/core/config'
|
|
23
|
+
export * from '@quiltt/core/observables'
|
|
24
|
+
export * from '@quiltt/core/storage'
|
|
25
|
+
export * from '@quiltt/core/timing'
|
|
26
|
+
export * from '@quiltt/core/types'
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Vue-specific exports
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// Quiltt Vue plugin, composables, and components for Vue 3 applications.
|
|
32
|
+
export * from './components'
|
|
33
|
+
export * from './composables'
|
|
34
|
+
export * from './plugin'
|