@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.
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Quiltt Vue Plugin implementation
3
+ *
4
+ * Provides session state management via Vue's provide/inject system.
5
+ * Handles token parsing, storage synchronization, and automatic expiration.
6
+ */
7
+
8
+ import type { App, Plugin } from 'vue'
9
+ import { ref, watch } from 'vue'
10
+
11
+ import type { Maybe, PrivateClaims, QuilttJWT } from '@quiltt/core'
12
+ import { JsonWebTokenParse } from '@quiltt/core'
13
+
14
+ import type { QuilttPluginOptions } from './keys'
15
+ import { QuilttClientIdKey, QuilttSessionKey, QuilttSetSessionKey } from './keys'
16
+
17
+ // Initialize JWT parser with our specific claims type
18
+ const parse = JsonWebTokenParse<PrivateClaims>
19
+
20
+ // Storage key for session persistence
21
+ const STORAGE_KEY = 'quiltt:session'
22
+
23
+ /**
24
+ * Get stored token from localStorage (browser only)
25
+ */
26
+ const getStoredToken = (): string | null => {
27
+ if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
28
+ return null
29
+ }
30
+ try {
31
+ return localStorage.getItem(STORAGE_KEY)
32
+ } catch {
33
+ return null
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Store token in localStorage (browser only)
39
+ */
40
+ const setStoredToken = (token: string | null): void => {
41
+ if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
42
+ return
43
+ }
44
+ try {
45
+ if (token) {
46
+ localStorage.setItem(STORAGE_KEY, token)
47
+ } else {
48
+ localStorage.removeItem(STORAGE_KEY)
49
+ }
50
+ } catch {
51
+ // Storage not available
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Quiltt Vue Plugin
57
+ *
58
+ * Provides session management across your Vue application.
59
+ * Use with `app.use(QuilttPlugin, options)`.
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * import { createApp } from 'vue'
64
+ * import { QuilttPlugin } from '@quiltt/vue'
65
+ *
66
+ * const app = createApp(App)
67
+ * app.use(QuilttPlugin, { token: '<SESSION_TOKEN>' })
68
+ * app.mount('#app')
69
+ * ```
70
+ */
71
+ export const QuilttPlugin: Plugin<[QuilttPluginOptions?]> = {
72
+ install(app: App, options?: QuilttPluginOptions) {
73
+ // Instance-scoped timeout for session expiration
74
+ let sessionTimeout: ReturnType<typeof setTimeout> | undefined
75
+ let isCleanedUp = false
76
+ let stopSessionWatcher: (() => void) | undefined
77
+
78
+ /**
79
+ * Clear the session timeout for this app instance
80
+ */
81
+ const clearSessionTimeout = () => {
82
+ if (sessionTimeout) {
83
+ clearTimeout(sessionTimeout)
84
+ sessionTimeout = undefined
85
+ }
86
+ }
87
+
88
+ // Initialize with provided token or stored token
89
+ const initialToken = options?.token ?? getStoredToken()
90
+ const initialSession = parse(initialToken)
91
+
92
+ // Reactive session state
93
+ const session = ref<Maybe<QuilttJWT> | undefined>(initialSession)
94
+ const clientId = ref<string | undefined>(options?.clientId)
95
+
96
+ /**
97
+ * Set session token
98
+ * Parses token, updates storage, and sets expiration timer
99
+ */
100
+ const setSession = (token: Maybe<string>): void => {
101
+ const parsed = parse(token)
102
+ session.value = parsed
103
+ setStoredToken(token ?? null)
104
+
105
+ // Clear any existing expiration timer
106
+ clearSessionTimeout()
107
+
108
+ // Set new expiration timer if session is valid
109
+ if (parsed) {
110
+ const expirationMS = parsed.claims.exp * 1000
111
+ const timeUntilExpiry = expirationMS - Date.now()
112
+
113
+ if (timeUntilExpiry > 0) {
114
+ sessionTimeout = setTimeout(() => {
115
+ session.value = null
116
+ setStoredToken(null)
117
+ }, timeUntilExpiry)
118
+ } else {
119
+ // Token already expired
120
+ session.value = null
121
+ setStoredToken(null)
122
+ }
123
+ }
124
+ }
125
+
126
+ // Storage event handler for cross-tab synchronization
127
+ let storageHandler: ((event: StorageEvent) => void) | undefined
128
+
129
+ // Listen for storage changes from other tabs/windows
130
+ if (typeof window !== 'undefined') {
131
+ storageHandler = (event: StorageEvent) => {
132
+ if (event.key === STORAGE_KEY) {
133
+ const newSession = parse(event.newValue)
134
+ session.value = newSession
135
+ }
136
+ }
137
+ window.addEventListener('storage', storageHandler)
138
+ }
139
+
140
+ // Cleanup function for when the app is unmounted
141
+ const cleanup = () => {
142
+ if (isCleanedUp) {
143
+ return
144
+ }
145
+ isCleanedUp = true
146
+
147
+ clearSessionTimeout()
148
+ if (stopSessionWatcher) {
149
+ stopSessionWatcher()
150
+ stopSessionWatcher = undefined
151
+ }
152
+ if (typeof window !== 'undefined' && storageHandler) {
153
+ window.removeEventListener('storage', storageHandler)
154
+ storageHandler = undefined
155
+ }
156
+ }
157
+
158
+ // Register cleanup on app unmount (Vue 3.5+)
159
+ if (typeof app.onUnmount === 'function') {
160
+ app.onUnmount(cleanup)
161
+ }
162
+
163
+ // Ensure cleanup runs on all supported Vue versions (3.3+)
164
+ if (typeof app.unmount === 'function') {
165
+ const originalUnmount = app.unmount.bind(app)
166
+ app.unmount = (...args: Parameters<typeof originalUnmount>) => {
167
+ cleanup()
168
+ return originalUnmount(...args)
169
+ }
170
+ }
171
+
172
+ // Watch for session changes to update expiration timer
173
+ stopSessionWatcher = watch(
174
+ () => session.value,
175
+ (newSession) => {
176
+ if (!newSession) {
177
+ clearSessionTimeout()
178
+ return
179
+ }
180
+
181
+ const expirationMS = newSession.claims.exp * 1000
182
+ const timeUntilExpiry = expirationMS - Date.now()
183
+
184
+ if (timeUntilExpiry <= 0) {
185
+ session.value = null
186
+ setStoredToken(null)
187
+ return
188
+ }
189
+
190
+ clearSessionTimeout()
191
+ sessionTimeout = setTimeout(() => {
192
+ session.value = null
193
+ setStoredToken(null)
194
+ }, timeUntilExpiry)
195
+ },
196
+ { immediate: true }
197
+ )
198
+
199
+ // Provide session state to all components
200
+ app.provide(QuilttSessionKey, session)
201
+ app.provide(QuilttSetSessionKey, setSession)
202
+ app.provide(QuilttClientIdKey, clientId)
203
+ },
204
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @quiltt/vue - Vue 3 Plugin
3
+ *
4
+ * Provides Quiltt session management via Vue's provide/inject system.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { createApp } from 'vue'
9
+ * import { QuilttPlugin } from '@quiltt/vue'
10
+ *
11
+ * const app = createApp(App)
12
+ * app.use(QuilttPlugin, { token: '<SESSION_TOKEN>' })
13
+ * app.mount('#app')
14
+ * ```
15
+ */
16
+
17
+ export {
18
+ QuilttClientIdKey,
19
+ type QuilttPluginOptions,
20
+ QuilttSessionKey,
21
+ QuilttSetSessionKey,
22
+ } from './keys'
23
+ export { QuilttPlugin } from './QuilttPlugin'
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Injection keys and types for Quiltt Vue plugin
3
+ */
4
+
5
+ import type { InjectionKey, Ref } from 'vue'
6
+
7
+ import type { Maybe, QuilttJWT } from '@quiltt/core'
8
+
9
+ // Injection keys for Quiltt state
10
+ export const QuilttSessionKey: InjectionKey<Ref<Maybe<QuilttJWT> | undefined>> =
11
+ Symbol.for('quiltt-session')
12
+ export const QuilttSetSessionKey: InjectionKey<(token: Maybe<string>) => void> =
13
+ Symbol.for('quiltt-set-session')
14
+ export const QuilttClientIdKey: InjectionKey<Ref<string | undefined>> =
15
+ Symbol.for('quiltt-client-id')
16
+
17
+ export interface QuilttPluginOptions {
18
+ /**
19
+ * Initial session token
20
+ */
21
+ token?: string
22
+ /**
23
+ * Quiltt Client ID (Environment ID)
24
+ */
25
+ clientId?: string
26
+ }
@@ -0,0 +1,7 @@
1
+ export {
2
+ getBrowserInfo,
3
+ getCapacitorInfo,
4
+ getPlatformInfo,
5
+ getSDKAgent,
6
+ getVueVersion,
7
+ } from './telemetry'
@@ -0,0 +1,73 @@
1
+ import { version as vueVersion } from 'vue'
2
+
3
+ import { getSDKAgent as coreGetSDKAgent, getBrowserInfo } from '@quiltt/core/utils'
4
+
5
+ // Re-export getBrowserInfo
6
+ export { getBrowserInfo }
7
+
8
+ // Capacitor global type declaration
9
+ declare global {
10
+ interface Window {
11
+ Capacitor?: {
12
+ isNativePlatform?: () => boolean
13
+ getPlatform?: () => string
14
+ }
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Gets the Vue version from the runtime
20
+ */
21
+ export const getVueVersion = (): string => {
22
+ return vueVersion || 'unknown'
23
+ }
24
+
25
+ /**
26
+ * Detects if running in a Capacitor native environment
27
+ */
28
+ export const getCapacitorInfo = (): string | null => {
29
+ if (typeof window === 'undefined') return null
30
+
31
+ try {
32
+ if (window.Capacitor?.isNativePlatform?.()) {
33
+ const platform = window.Capacitor.getPlatform?.() || 'native'
34
+ // Map platform names to correct capitalization
35
+ const platformNames: Record<string, string> = {
36
+ ios: 'iOS',
37
+ android: 'Android',
38
+ web: 'Web',
39
+ }
40
+ const platformName = platformNames[platform.toLowerCase()] || platform
41
+ return `Capacitor/${platformName}`
42
+ }
43
+ } catch {
44
+ // Ignore errors
45
+ }
46
+ return null
47
+ }
48
+
49
+ /**
50
+ * Generates platform information string for Vue web
51
+ * Format: Vue/<version>; <browser>/<version>
52
+ * Or with Capacitor: Vue/<version>; Capacitor/<platform>; <browser>/<version>
53
+ */
54
+ export const getPlatformInfo = (): string => {
55
+ const versionStr = getVueVersion()
56
+ const capacitorInfo = getCapacitorInfo()
57
+ const browserInfo = getBrowserInfo()
58
+
59
+ if (capacitorInfo) {
60
+ return `Vue/${versionStr}; ${capacitorInfo}; ${browserInfo}`
61
+ }
62
+
63
+ return `Vue/${versionStr}; ${browserInfo}`
64
+ }
65
+
66
+ /**
67
+ * Generates User-Agent string for Vue SDK
68
+ * Format: Quiltt/<sdk-version> (Vue/<vue-version>; <browser>/<version>)
69
+ */
70
+ export const getSDKAgent = (sdkVersion: string): string => {
71
+ const platformInfo = getPlatformInfo()
72
+ return coreGetSDKAgent(sdkVersion, platformInfo)
73
+ }
package/src/version.ts ADDED
@@ -0,0 +1 @@
1
+ export { version } from '../package.json'