@codingfactory/socialkit-vue 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/composables/useAuth.d.ts +27 -0
  2. package/dist/composables/useAuth.d.ts.map +1 -0
  3. package/dist/composables/useAuth.js +137 -0
  4. package/dist/composables/useAuth.js.map +1 -0
  5. package/dist/index.d.ts +25 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +16 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/plugins/terminology/index.d.ts +11 -0
  10. package/dist/plugins/terminology/index.d.ts.map +1 -0
  11. package/dist/plugins/terminology/index.js +91 -0
  12. package/dist/plugins/terminology/index.js.map +1 -0
  13. package/dist/plugins/terminology/terms.d.ts +15 -0
  14. package/dist/plugins/terminology/terms.d.ts.map +1 -0
  15. package/dist/plugins/terminology/terms.js +72 -0
  16. package/dist/plugins/terminology/terms.js.map +1 -0
  17. package/dist/plugins/terminology/types.d.ts +32 -0
  18. package/dist/plugins/terminology/types.d.ts.map +1 -0
  19. package/dist/plugins/terminology/types.js +2 -0
  20. package/dist/plugins/terminology/types.js.map +1 -0
  21. package/dist/services/api.d.ts +50 -0
  22. package/dist/services/api.d.ts.map +1 -0
  23. package/dist/services/api.js +305 -0
  24. package/dist/services/api.js.map +1 -0
  25. package/dist/services/auth.d.ts +127 -0
  26. package/dist/services/auth.d.ts.map +1 -0
  27. package/dist/services/auth.js +562 -0
  28. package/dist/services/auth.js.map +1 -0
  29. package/dist/stores/auth.d.ts +174 -0
  30. package/dist/stores/auth.d.ts.map +1 -0
  31. package/dist/stores/auth.js +262 -0
  32. package/dist/stores/auth.js.map +1 -0
  33. package/dist/types/api.d.ts +52 -0
  34. package/dist/types/api.d.ts.map +1 -0
  35. package/dist/types/api.js +7 -0
  36. package/dist/types/api.js.map +1 -0
  37. package/dist/types/user.d.ts +42 -0
  38. package/dist/types/user.d.ts.map +1 -0
  39. package/dist/types/user.js +45 -0
  40. package/dist/types/user.js.map +1 -0
  41. package/dist/utils/tokenStorage.d.ts +41 -0
  42. package/dist/utils/tokenStorage.d.ts.map +1 -0
  43. package/dist/utils/tokenStorage.js +300 -0
  44. package/dist/utils/tokenStorage.js.map +1 -0
  45. package/package.json +40 -0
  46. package/src/composables/useAuth.ts +164 -0
  47. package/src/index.ts +118 -0
  48. package/src/plugins/terminology/index.ts +114 -0
  49. package/src/plugins/terminology/terms.ts +104 -0
  50. package/src/plugins/terminology/types.ts +28 -0
  51. package/src/services/api.ts +472 -0
  52. package/src/services/auth.ts +874 -0
  53. package/src/stores/auth.ts +400 -0
  54. package/src/types/api.ts +56 -0
  55. package/src/types/user.ts +94 -0
  56. package/src/utils/tokenStorage.ts +394 -0
@@ -0,0 +1,400 @@
1
+ /**
2
+ * Generic auth store factory for SocialKit-powered frontends.
3
+ *
4
+ * Use `createAuthStoreDefinition()` to produce a Pinia store definition
5
+ * that each site frontend can register with its own configuration.
6
+ */
7
+
8
+ import { defineStore } from 'pinia'
9
+ import { ref, computed } from 'vue'
10
+ import type { ComputedRef, Ref } from 'vue'
11
+ import type { Router } from 'vue-router'
12
+ import type { AuthServiceInstance, LoginResponse, RegisterData } from '../services/auth.js'
13
+ import type { ApiService } from '../services/api.js'
14
+ import type { TokenStorage } from '../utils/tokenStorage.js'
15
+ import type { User } from '../types/user.js'
16
+
17
+ export interface AuthStoreStateRefs {
18
+ user: Ref<User | null>
19
+ isAuthenticated: Ref<boolean>
20
+ loading: Ref<boolean>
21
+ }
22
+
23
+ export interface AuthStoreGetters {
24
+ currentUser: ComputedRef<User | null>
25
+ isLoggedIn: ComputedRef<boolean>
26
+ token: ComputedRef<string | null>
27
+ }
28
+
29
+ export interface AuthStoreActions {
30
+ login: (loginCredential: string, password: string, redirectPath?: string, remember?: boolean) => Promise<LoginResponse>
31
+ register: (data: RegisterData) => Promise<LoginResponse>
32
+ logout: () => Promise<void>
33
+ refreshSessionAndReplay: () => Promise<boolean>
34
+ fetchUser: () => Promise<User | null>
35
+ checkAuth: (forceValidation?: boolean) => Promise<void>
36
+ setUser: (newUser: User) => void
37
+ hasStoredToken: () => boolean
38
+ }
39
+
40
+ export interface AuthStoreExtensionContext {
41
+ state: AuthStoreStateRefs
42
+ getters: AuthStoreGetters
43
+ actions: AuthStoreActions
44
+ services: {
45
+ authService: AuthServiceInstance
46
+ apiService: ApiService
47
+ tokenStorage: TokenStorage
48
+ router: Router
49
+ }
50
+ config: {
51
+ storeId: string
52
+ defaultLoginRedirect: string
53
+ }
54
+ resolveSafeRedirectPath: (redirectPath?: string) => string
55
+ }
56
+
57
+ export type AuthStoreExtension = Record<string, unknown>
58
+
59
+ /** Configuration for creating the auth store definition. */
60
+ export interface AuthStoreConfig<TExtension extends AuthStoreExtension = Record<string, never>> {
61
+ /** Pinia store id (default: 'auth'). */
62
+ storeId?: string
63
+ /** Auth service instance. */
64
+ authService: AuthServiceInstance
65
+ /** API service instance (for pending auth request management). */
66
+ apiService: ApiService
67
+ /** Token storage instance. */
68
+ tokenStorage: TokenStorage
69
+ /** Vue Router instance. */
70
+ router: Router
71
+ /** Default redirect path after login (default: '/'). */
72
+ defaultLoginRedirect?: string
73
+ /** Extra Pinia store options (e.g. persist config from pinia-plugin-persistedstate). */
74
+ storeOptions?: Record<string, unknown>
75
+ /** Hook called during logout to reset app-specific stores. */
76
+ onLogout?: () => Promise<void> | void
77
+ /** Hook called after the user object is updated. */
78
+ onUserUpdated?: (prev: User | null, next: User) => void
79
+ /** Callback to set the validating-auth flag on the auth service layer. */
80
+ setValidatingAuth?: (value: boolean) => void
81
+ /** Add app-specific state, getters, and actions without forking the generic auth core. */
82
+ extend?: (context: AuthStoreExtensionContext) => TExtension
83
+ }
84
+
85
+ export type AuthStoreReturn = ReturnType<ReturnType<typeof createAuthStoreDefinition>>
86
+
87
+ /** Create a Pinia auth store definition configured for a specific site. */
88
+ export function createAuthStoreDefinition<TExtension extends AuthStoreExtension = Record<string, never>>(config: AuthStoreConfig<TExtension>) {
89
+ const {
90
+ storeId = 'auth',
91
+ authService,
92
+ apiService,
93
+ tokenStorage,
94
+ router,
95
+ defaultLoginRedirect = '/',
96
+ storeOptions = {},
97
+ onLogout,
98
+ onUserUpdated,
99
+ setValidatingAuth,
100
+ extend
101
+ } = config
102
+
103
+ const EXTERNAL_PROTOCOL_PATTERN = /^[a-z][a-z\d+\-.]*:/i
104
+
105
+ return defineStore(storeId, () => {
106
+ // State
107
+ const user = ref<User | null>(null)
108
+ const isAuthenticated = ref(false)
109
+ const loading = ref(false)
110
+
111
+ // Getters
112
+ const currentUser = computed(() => user.value)
113
+ const isLoggedIn = computed(() => isAuthenticated.value)
114
+ const token = computed<string | null>(() => tokenStorage.getToken())
115
+
116
+ function resolveSafeRedirectPath(redirectPath?: string): string {
117
+ if (!redirectPath) {
118
+ return defaultLoginRedirect
119
+ }
120
+
121
+ const trimmed = redirectPath.trim()
122
+
123
+ if (trimmed === '') {
124
+ return defaultLoginRedirect
125
+ }
126
+
127
+ if (EXTERNAL_PROTOCOL_PATTERN.test(trimmed)) {
128
+ return defaultLoginRedirect
129
+ }
130
+
131
+ if (!trimmed.startsWith('/') || trimmed.startsWith('//')) {
132
+ return defaultLoginRedirect
133
+ }
134
+
135
+ return trimmed
136
+ }
137
+
138
+ // Actions
139
+ async function login(loginCredential: string, password: string, redirectPath?: string, remember = false) {
140
+ loading.value = true
141
+
142
+ try {
143
+ const response = await authService.login(loginCredential, password, remember)
144
+ user.value = response.user
145
+ isAuthenticated.value = true
146
+
147
+ await fetchUser()
148
+
149
+ const destination = resolveSafeRedirectPath(redirectPath)
150
+ router.push(destination)
151
+
152
+ return response
153
+ } catch (error) {
154
+ isAuthenticated.value = false
155
+ user.value = null
156
+ throw error
157
+ } finally {
158
+ loading.value = false
159
+ }
160
+ }
161
+
162
+ async function register(data: {
163
+ handle: string
164
+ name: string
165
+ email: string
166
+ password: string
167
+ password_confirmation: string
168
+ }) {
169
+ loading.value = true
170
+
171
+ try {
172
+ const response = await authService.register(data)
173
+ user.value = response.user
174
+ isAuthenticated.value = true
175
+
176
+ await fetchUser()
177
+ router.push(defaultLoginRedirect)
178
+
179
+ return response
180
+ } catch (error) {
181
+ isAuthenticated.value = false
182
+ user.value = null
183
+ throw error
184
+ } finally {
185
+ loading.value = false
186
+ }
187
+ }
188
+
189
+ async function logout() {
190
+ loading.value = true
191
+
192
+ try {
193
+ await authService.logout()
194
+ } finally {
195
+ apiService.clearPendingAuthRequest()
196
+
197
+ if (onLogout) {
198
+ try {
199
+ await onLogout()
200
+ } catch {
201
+ // Swallow store-reset errors during logout
202
+ }
203
+ }
204
+
205
+ user.value = null
206
+ isAuthenticated.value = false
207
+ loading.value = false
208
+
209
+ const currentPath = window.location.pathname
210
+ if (currentPath !== '/login' && currentPath !== '/register') {
211
+ router.push('/login')
212
+ }
213
+ }
214
+ }
215
+
216
+ async function refreshSessionAndReplay(): Promise<boolean> {
217
+ const refreshedToken = await tokenStorage.tryRefreshToken()
218
+
219
+ if (!refreshedToken) {
220
+ apiService.clearPendingAuthRequest()
221
+ user.value = null
222
+ isAuthenticated.value = false
223
+ tokenStorage.removeToken()
224
+ return false
225
+ }
226
+
227
+ try {
228
+ await fetchUser()
229
+ } catch {
230
+ apiService.clearPendingAuthRequest()
231
+ user.value = null
232
+ isAuthenticated.value = false
233
+ tokenStorage.removeToken()
234
+ return false
235
+ }
236
+
237
+ apiService.markAuthRecoveryHandled()
238
+
239
+ if (apiService.hasPendingAuthRequest()) {
240
+ try {
241
+ await apiService.replayPendingAuthRequest(refreshedToken)
242
+ } catch {
243
+ // Keep session; callers can retry replay manually
244
+ }
245
+ }
246
+
247
+ return true
248
+ }
249
+
250
+ async function fetchUser() {
251
+ loading.value = true
252
+
253
+ try {
254
+ const prev = user.value ? { ...user.value } : null
255
+ const next = await authService.getUser()
256
+
257
+ user.value = { ...next }
258
+ isAuthenticated.value = true
259
+
260
+ if (onUserUpdated) {
261
+ onUserUpdated(prev, next)
262
+ }
263
+
264
+ return user.value
265
+ } catch (error) {
266
+ isAuthenticated.value = false
267
+ user.value = null
268
+ throw error
269
+ } finally {
270
+ loading.value = false
271
+ }
272
+ }
273
+
274
+ async function checkAuth(forceValidation = false) {
275
+ const storedToken = tokenStorage.getToken()
276
+
277
+ if (!storedToken) {
278
+ isAuthenticated.value = false
279
+ user.value = null
280
+ return
281
+ }
282
+
283
+ if (!forceValidation && isAuthenticated.value && user.value) {
284
+ return
285
+ }
286
+
287
+ if (setValidatingAuth) {
288
+ setValidatingAuth(true)
289
+ }
290
+
291
+ try {
292
+ await fetchUser()
293
+ } catch {
294
+ isAuthenticated.value = false
295
+ user.value = null
296
+ tokenStorage.removeToken()
297
+ } finally {
298
+ if (setValidatingAuth) {
299
+ setValidatingAuth(false)
300
+ }
301
+ }
302
+ }
303
+
304
+ function setUser(newUser: User) {
305
+ const prev = user.value ? { ...user.value } : null
306
+ user.value = { ...newUser }
307
+ isAuthenticated.value = true
308
+
309
+ if (onUserUpdated) {
310
+ onUserUpdated(prev, newUser)
311
+ }
312
+ }
313
+
314
+ function hasStoredToken(): boolean {
315
+ const storedToken = tokenStorage.getToken()
316
+
317
+ if (storedToken === null) {
318
+ isAuthenticated.value = false
319
+ user.value = null
320
+ return false
321
+ }
322
+
323
+ return true
324
+ }
325
+
326
+ const baseState: AuthStoreStateRefs = {
327
+ user,
328
+ isAuthenticated,
329
+ loading
330
+ }
331
+
332
+ const baseGetters: AuthStoreGetters = {
333
+ currentUser,
334
+ isLoggedIn,
335
+ token
336
+ }
337
+
338
+ const baseActions: AuthStoreActions = {
339
+ login,
340
+ register,
341
+ logout,
342
+ refreshSessionAndReplay,
343
+ fetchUser,
344
+ checkAuth,
345
+ setUser,
346
+ hasStoredToken
347
+ }
348
+
349
+ const baseStore = {
350
+ // State (as computed refs for readonly exposure)
351
+ user: computed(() => user.value),
352
+ isAuthenticated: computed(() => isAuthenticated.value && token.value !== null),
353
+ loading: computed(() => loading.value),
354
+
355
+ // Getters
356
+ ...baseGetters,
357
+
358
+ // Actions
359
+ ...baseActions
360
+ }
361
+
362
+ const extensionContext: AuthStoreExtensionContext = {
363
+ state: baseState,
364
+ getters: baseGetters,
365
+ actions: baseActions,
366
+ services: {
367
+ authService,
368
+ apiService,
369
+ tokenStorage,
370
+ router
371
+ },
372
+ config: {
373
+ storeId,
374
+ defaultLoginRedirect
375
+ },
376
+ resolveSafeRedirectPath
377
+ }
378
+
379
+ const extension = extend?.(extensionContext)
380
+
381
+ if (extension) {
382
+ for (const key of Object.keys(extension)) {
383
+ if (key in baseStore) {
384
+ throw new Error(`Auth store extension key "${key}" conflicts with the shared auth store surface.`)
385
+ }
386
+ }
387
+ }
388
+
389
+ if (extension) {
390
+ return {
391
+ ...baseStore,
392
+ ...extension
393
+ }
394
+ }
395
+
396
+ return {
397
+ ...baseStore
398
+ }
399
+ }, storeOptions as Record<string, never>)
400
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Core type definitions for SocialKit API services.
3
+ *
4
+ * These types are generic and shared across all SocialKit-powered frontends.
5
+ */
6
+
7
+ /** Standard API response wrapper. */
8
+ export interface ApiResponse<T> {
9
+ data: T
10
+ meta?: {
11
+ current_page?: number
12
+ total?: number
13
+ per_page?: number
14
+ last_page?: number
15
+ next_cursor?: string
16
+ prev_cursor?: string
17
+ has_more?: boolean
18
+ }
19
+ message?: string
20
+ status?: number
21
+ }
22
+
23
+ /** Paginated response shape. */
24
+ export interface PaginatedResponse<T> {
25
+ items: T[]
26
+ total: number
27
+ per_page: number
28
+ current_page: number
29
+ last_page: number
30
+ next_cursor?: string
31
+ prev_cursor?: string
32
+ }
33
+
34
+ /** API error shape. */
35
+ export interface ApiError {
36
+ message: string
37
+ code?: string
38
+ status?: number
39
+ details?: Record<string, unknown>
40
+ }
41
+
42
+ /** Upload progress event. */
43
+ export interface UploadProgressEvent {
44
+ loaded: number
45
+ total?: number
46
+ progress?: number
47
+ }
48
+
49
+ /** Request configuration accepted by ApiService methods. */
50
+ export interface RequestConfig {
51
+ headers?: Record<string, string>
52
+ params?: Record<string, string | number | boolean>
53
+ timeout?: number
54
+ onUploadProgress?: (progressEvent: UploadProgressEvent) => void
55
+ signal?: AbortSignal
56
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * User type definitions shared across SocialKit frontends.
3
+ */
4
+
5
+ /** Base user type used by the auth system. */
6
+ export interface User {
7
+ id: string
8
+ name: string
9
+ email: string
10
+ avatar?: string | Record<string, unknown> | null
11
+ avatar_url?: string | null
12
+ cover_photo?: string | null
13
+ handle?: string
14
+ role: 'admin' | 'moderator' | 'user'
15
+ tenant_id?: string
16
+ roles?: string[]
17
+ permissions?: string[]
18
+ capabilities?: Record<string, boolean>
19
+ last_login_at?: string
20
+ created_at?: string
21
+ status?: 'active' | 'suspended' | 'banned'
22
+ model_type?: string
23
+ }
24
+
25
+ /** Identity user type from the identity service. */
26
+ export interface IdentityUser {
27
+ id: string
28
+ handle?: string
29
+ name: string
30
+ avatar?: string | null
31
+ avatar_url?: string | null
32
+ cover_photo?: string | null
33
+ bio?: string | null
34
+ email?: string
35
+ created_at?: string
36
+ updated_at?: string
37
+ }
38
+
39
+ /** Type guard for User. */
40
+ export function isUser(obj: unknown): obj is User {
41
+ return (
42
+ obj !== null &&
43
+ typeof obj === 'object' &&
44
+ 'id' in obj &&
45
+ 'name' in obj &&
46
+ 'email' in obj &&
47
+ 'role' in obj
48
+ )
49
+ }
50
+
51
+ /** Type guard for IdentityUser. */
52
+ export function isIdentityUser(obj: unknown): obj is IdentityUser {
53
+ return (
54
+ obj !== null &&
55
+ typeof obj === 'object' &&
56
+ 'id' in obj &&
57
+ 'name' in obj
58
+ )
59
+ }
60
+
61
+ /** Convert an IdentityUser to the base User type. */
62
+ export function identityUserToUser(
63
+ identity: IdentityUser,
64
+ role: 'admin' | 'moderator' | 'user' = 'user'
65
+ ): User {
66
+ const user: User = {
67
+ id: identity.id,
68
+ name: identity.name,
69
+ email: identity.email ?? '',
70
+ role
71
+ }
72
+
73
+ if (identity.avatar !== null && identity.avatar !== undefined) {
74
+ user.avatar = identity.avatar
75
+ }
76
+
77
+ if (identity.avatar_url !== null && identity.avatar_url !== undefined) {
78
+ user.avatar_url = identity.avatar_url
79
+ }
80
+
81
+ if (identity.cover_photo !== null && identity.cover_photo !== undefined) {
82
+ user.cover_photo = identity.cover_photo
83
+ }
84
+
85
+ if (identity.handle !== undefined) {
86
+ user.handle = identity.handle
87
+ }
88
+
89
+ if (identity.created_at !== undefined) {
90
+ user.created_at = identity.created_at
91
+ }
92
+
93
+ return user
94
+ }