@take-out/better-auth-utils 0.0.28
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 +21 -0
- package/README.md +137 -0
- package/dist/cjs/createAuthClient.cjs +172 -0
- package/dist/cjs/createAuthClient.js +153 -0
- package/dist/cjs/createAuthClient.js.map +6 -0
- package/dist/cjs/createAuthClient.native.js +161 -0
- package/dist/cjs/createAuthClient.native.js.map +6 -0
- package/dist/cjs/index.cjs +18 -0
- package/dist/cjs/index.js +15 -0
- package/dist/cjs/index.js.map +6 -0
- package/dist/cjs/index.native.js +20 -0
- package/dist/cjs/index.native.js.map +6 -0
- package/dist/esm/createAuthClient.js +143 -0
- package/dist/esm/createAuthClient.js.map +6 -0
- package/dist/esm/createAuthClient.mjs +149 -0
- package/dist/esm/createAuthClient.mjs.map +1 -0
- package/dist/esm/createAuthClient.native.js +164 -0
- package/dist/esm/createAuthClient.native.js.map +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +6 -0
- package/dist/esm/index.mjs +2 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/index.native.js +2 -0
- package/dist/esm/index.native.js.map +1 -0
- package/package.json +64 -0
- package/src/createAuthClient.ts +324 -0
- package/src/index.ts +1 -0
- package/types/client.d.ts +10 -0
- package/types/client.d.ts.map +11 -0
- package/types/createAuthClient.d.ts +81 -0
- package/types/createAuthClient.d.ts.map +18 -0
- package/types/hooks/useAuthSession.d.ts +8 -0
- package/types/hooks/useAuthSession.d.ts.map +13 -0
- package/types/hooks/useAuthState.d.ts +7 -0
- package/types/hooks/useAuthState.d.ts.map +13 -0
- package/types/hooks/useAuthUser.d.ts +8 -0
- package/types/hooks/useAuthUser.d.ts.map +13 -0
- package/types/index.d.ts +3 -0
- package/types/index.d.ts.map +11 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Better-auth helpers for React / React Native applications
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - JWT support (for Zero, Tauri, React Native)
|
|
6
|
+
* - Session persistence in local storage
|
|
7
|
+
* - Token validation and refresh
|
|
8
|
+
* - State management with emitters
|
|
9
|
+
* - Automatic retry on errors
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
createEmitter,
|
|
14
|
+
createLocalStorageValue,
|
|
15
|
+
type Emitter,
|
|
16
|
+
isEqualDeepLite,
|
|
17
|
+
useEmitterValue,
|
|
18
|
+
} from '@take-out/helpers'
|
|
19
|
+
import type { Session, User } from 'better-auth'
|
|
20
|
+
import { type BetterAuthClientOptions, createAuthClient } from 'better-auth/client'
|
|
21
|
+
|
|
22
|
+
export interface StorageKeys {
|
|
23
|
+
token: string
|
|
24
|
+
session: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type AuthState<U extends User = User> = {
|
|
28
|
+
state: 'loading' | 'logged-in' | 'logged-out'
|
|
29
|
+
session: Session | null
|
|
30
|
+
user: U | null
|
|
31
|
+
token: string | null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface BetterAuthClientProps<TUser extends User = User>
|
|
35
|
+
extends BetterAuthClientOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Callback to transform and type the user object
|
|
38
|
+
* This allows you to add app-specific fields and ensure proper typing
|
|
39
|
+
* @default (user) => user
|
|
40
|
+
*/
|
|
41
|
+
createUser?: (user: User) => TUser
|
|
42
|
+
/**
|
|
43
|
+
* Optional callback when authentication state changes
|
|
44
|
+
*/
|
|
45
|
+
onAuthStateChange?: (state: AuthState<TUser>) => void
|
|
46
|
+
/**
|
|
47
|
+
* Optional callback for handling auth errors
|
|
48
|
+
*/
|
|
49
|
+
onAuthError?: (error: any) => void
|
|
50
|
+
/**
|
|
51
|
+
* Storage key prefix for local storage
|
|
52
|
+
* @default 'auth'
|
|
53
|
+
*/
|
|
54
|
+
storagePrefix?: string
|
|
55
|
+
/**
|
|
56
|
+
* Retry delay in milliseconds after auth errors
|
|
57
|
+
* @default 4000
|
|
58
|
+
*/
|
|
59
|
+
retryDelay?: number
|
|
60
|
+
/**
|
|
61
|
+
* Custom token validation endpoint
|
|
62
|
+
* @default '/api/auth/validateToken'
|
|
63
|
+
*/
|
|
64
|
+
tokenValidationEndpoint?: string
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface BetterAuthClientReturn<U extends User = User, TClient = any> {
|
|
68
|
+
clearState: () => void
|
|
69
|
+
authState: ReturnType<typeof createEmitter<AuthState<U>>>
|
|
70
|
+
authClient: TClient
|
|
71
|
+
setAuthClientToken: (props: { token: string; session: string }) => Promise<void>
|
|
72
|
+
clearAuthClientToken: () => void
|
|
73
|
+
useAuth: () => AuthState<U>
|
|
74
|
+
getAuth: () => AuthState<U> & { loggedIn: boolean }
|
|
75
|
+
getValidToken: () => Promise<string | undefined>
|
|
76
|
+
updateAuthClient: (session: string) => void
|
|
77
|
+
authClientVersion: Emitter<number>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
type InferUser<T> = T extends { createUser?: (user: User) => infer R }
|
|
81
|
+
? R extends User
|
|
82
|
+
? R
|
|
83
|
+
: User
|
|
84
|
+
: User
|
|
85
|
+
|
|
86
|
+
export function createBetterAuthClient<const Opts extends BetterAuthClientProps<any>>(
|
|
87
|
+
options: Opts
|
|
88
|
+
): BetterAuthClientReturn<InferUser<Opts>, ReturnType<typeof createAuthClient<Opts>>> {
|
|
89
|
+
type TUser = InferUser<Opts>
|
|
90
|
+
const {
|
|
91
|
+
onAuthStateChange,
|
|
92
|
+
onAuthError,
|
|
93
|
+
createUser,
|
|
94
|
+
storagePrefix = 'auth',
|
|
95
|
+
retryDelay = 4000,
|
|
96
|
+
tokenValidationEndpoint = '/api/auth/validateToken',
|
|
97
|
+
...authClientOptions
|
|
98
|
+
} = options
|
|
99
|
+
|
|
100
|
+
const empty: AuthState<TUser> = {
|
|
101
|
+
state: 'logged-out',
|
|
102
|
+
session: null,
|
|
103
|
+
user: null,
|
|
104
|
+
token: null,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const createAuthClientWithSession = (session: string) => {
|
|
108
|
+
return createAuthClient({
|
|
109
|
+
...authClientOptions,
|
|
110
|
+
fetchOptions: {
|
|
111
|
+
headers: {
|
|
112
|
+
Authorization: `Bearer ${session}`,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const keysStorage = createLocalStorageValue<StorageKeys>(`${storagePrefix}-keys`)
|
|
119
|
+
const stateStorage = createLocalStorageValue<AuthState<TUser>>(`${storagePrefix}-state`)
|
|
120
|
+
|
|
121
|
+
let authClient = (() => {
|
|
122
|
+
const existingSession = keysStorage.get()?.session
|
|
123
|
+
return existingSession
|
|
124
|
+
? createAuthClientWithSession(existingSession)
|
|
125
|
+
: createAuthClient(authClientOptions as Opts)
|
|
126
|
+
})()
|
|
127
|
+
|
|
128
|
+
const authState = createEmitter<AuthState<TUser>>(
|
|
129
|
+
'authState',
|
|
130
|
+
stateStorage.get() || empty,
|
|
131
|
+
{
|
|
132
|
+
comparator: isEqualDeepLite,
|
|
133
|
+
}
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
const authClientVersion = createEmitter<number>('authClientVersion', 0)
|
|
137
|
+
|
|
138
|
+
const setState = (update: Partial<AuthState<TUser>>) => {
|
|
139
|
+
const current = authState.value!
|
|
140
|
+
const next = { ...current, ...update }
|
|
141
|
+
stateStorage.set(next)
|
|
142
|
+
authState.emit(next)
|
|
143
|
+
|
|
144
|
+
// update storage keys
|
|
145
|
+
if (next.token && next.session) {
|
|
146
|
+
keysStorage.set({
|
|
147
|
+
token: next.token,
|
|
148
|
+
session: next.session.token,
|
|
149
|
+
})
|
|
150
|
+
} else {
|
|
151
|
+
keysStorage.set({
|
|
152
|
+
token: '',
|
|
153
|
+
session: '',
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// call optional callback
|
|
158
|
+
onAuthStateChange?.(next)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const setAuthClientToken = async (props: { token: string; session: string }) => {
|
|
162
|
+
keysStorage.set(props)
|
|
163
|
+
updateAuthClient(props.session)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function updateAuthClient(session: string) {
|
|
167
|
+
authClient = createAuthClientWithSession(session)
|
|
168
|
+
authClientVersion.emit(Math.random())
|
|
169
|
+
subscribeToAuthEffect()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let dispose: Function | null = null
|
|
173
|
+
let retryTimer: ReturnType<typeof setTimeout> | null = null
|
|
174
|
+
|
|
175
|
+
function subscribeToAuthEffect() {
|
|
176
|
+
dispose?.()
|
|
177
|
+
|
|
178
|
+
dispose = authClient.useSession.subscribe(async (props) => {
|
|
179
|
+
const { data: dataGeneric, isPending, error } = props
|
|
180
|
+
|
|
181
|
+
if (error) {
|
|
182
|
+
onAuthError?.(error)
|
|
183
|
+
// retry by re-subscribing after a delay to recover from transient errors
|
|
184
|
+
scheduleAuthRetry(retryDelay)
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const data = dataGeneric as
|
|
189
|
+
| undefined
|
|
190
|
+
| {
|
|
191
|
+
session?: AuthState<TUser>['session']
|
|
192
|
+
user?: AuthState<TUser>['user']
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
setState({
|
|
196
|
+
state: isPending ? 'loading' : data?.session ? 'logged-in' : 'logged-out',
|
|
197
|
+
session: data?.session,
|
|
198
|
+
user: data?.user
|
|
199
|
+
? createUser
|
|
200
|
+
? createUser(data.user as User)
|
|
201
|
+
: (data.user as any)
|
|
202
|
+
: null,
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
// get token on creating a new session (for native + tauri)
|
|
206
|
+
if (data?.session && !authState.value.token) {
|
|
207
|
+
getValidToken().then((token) => {
|
|
208
|
+
if (token) {
|
|
209
|
+
setState({ token })
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function scheduleAuthRetry(delayMs: number) {
|
|
217
|
+
if (retryTimer) {
|
|
218
|
+
clearTimeout(retryTimer)
|
|
219
|
+
}
|
|
220
|
+
retryTimer = setTimeout(() => {
|
|
221
|
+
retryTimer = null
|
|
222
|
+
subscribeToAuthEffect()
|
|
223
|
+
}, delayMs)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function getValidToken(): Promise<string | undefined> {
|
|
227
|
+
const existing = keysStorage.get()?.token
|
|
228
|
+
|
|
229
|
+
if (existing) {
|
|
230
|
+
try {
|
|
231
|
+
const response = await fetch(tokenValidationEndpoint, {
|
|
232
|
+
method: 'POST',
|
|
233
|
+
headers: {
|
|
234
|
+
'Content-Type': 'application/json',
|
|
235
|
+
},
|
|
236
|
+
body: JSON.stringify({
|
|
237
|
+
token: existing,
|
|
238
|
+
}),
|
|
239
|
+
}).then((res) => res.json())
|
|
240
|
+
|
|
241
|
+
if (response?.valid) {
|
|
242
|
+
return existing
|
|
243
|
+
}
|
|
244
|
+
} catch (error) {
|
|
245
|
+
console.error('Error validating token:', error)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const res = await authClient.$fetch('/token')
|
|
250
|
+
if (res.error) {
|
|
251
|
+
console.error(`Error fetching token: ${res.error.statusText}`)
|
|
252
|
+
return undefined
|
|
253
|
+
}
|
|
254
|
+
const data = res.data as any
|
|
255
|
+
return data?.token as string | undefined
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const clearAuthClientToken = () => {
|
|
259
|
+
keysStorage.remove()
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const getAuth = () => {
|
|
263
|
+
const state = authState?.value || empty
|
|
264
|
+
return {
|
|
265
|
+
...state,
|
|
266
|
+
loggedIn: !!state.session,
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const useAuth = () => {
|
|
271
|
+
return useEmitterValue(authState) || empty
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function clearState() {
|
|
275
|
+
keysStorage.clear()
|
|
276
|
+
stateStorage.clear()
|
|
277
|
+
setState(empty)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// initialize subscription
|
|
281
|
+
subscribeToAuthEffect()
|
|
282
|
+
|
|
283
|
+
// cleanup on unmount
|
|
284
|
+
if (typeof window !== 'undefined' && window.addEventListener) {
|
|
285
|
+
const cleanup = () => {
|
|
286
|
+
dispose?.()
|
|
287
|
+
if (retryTimer) {
|
|
288
|
+
clearTimeout(retryTimer)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
window.addEventListener('beforeunload', cleanup)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const proxiedAuthClient = new Proxy(authClient, {
|
|
295
|
+
get(_target, key) {
|
|
296
|
+
if (key === 'signOut') {
|
|
297
|
+
return async () => {
|
|
298
|
+
clearState()
|
|
299
|
+
// @ts-expect-error better-auth type issue, signOut does exist
|
|
300
|
+
await authClient.signOut?.()
|
|
301
|
+
// force refresh to clear any remaining state
|
|
302
|
+
if (typeof window !== 'undefined') {
|
|
303
|
+
window.location?.reload?.()
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// ensure always latest authClient after login
|
|
308
|
+
return Reflect.get(authClient, key)
|
|
309
|
+
},
|
|
310
|
+
}) as ReturnType<typeof createAuthClient<Opts>>
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
authClientVersion,
|
|
314
|
+
clearState,
|
|
315
|
+
authState,
|
|
316
|
+
authClient: proxiedAuthClient,
|
|
317
|
+
setAuthClientToken,
|
|
318
|
+
clearAuthClientToken,
|
|
319
|
+
useAuth,
|
|
320
|
+
getAuth,
|
|
321
|
+
getValidToken,
|
|
322
|
+
updateAuthClient,
|
|
323
|
+
}
|
|
324
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './createAuthClient'
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-specific exports for better-auth
|
|
3
|
+
*/
|
|
4
|
+
export { createBetterAuthClient } from "./createAuthClient";
|
|
5
|
+
export type { AuthState, BetterAuthClientConfig, BetterAuthClientReturn, StorageKeys } from "./createAuthClient";
|
|
6
|
+
// re-export useful better-auth types
|
|
7
|
+
export type { BetterAuthClientOptions, BetterAuthClientPlugin } from "better-auth/client";
|
|
8
|
+
export type { Session, User } from "better-auth";
|
|
9
|
+
|
|
10
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mappings": ";;;AAIA,SAAS,8BAA8B;AACvC,cACE,WACA,wBACA,wBACA,mBACK;;AAGP,cAAc,yBAAyB,8BAA8B;AACrE,cAAc,SAAS,YAAY",
|
|
3
|
+
"names": [],
|
|
4
|
+
"sources": [
|
|
5
|
+
"src/client.ts"
|
|
6
|
+
],
|
|
7
|
+
"sourcesContent": [
|
|
8
|
+
"/**\n * Client-specific exports for better-auth\n */\n\nexport { createBetterAuthClient } from './createAuthClient'\nexport type {\n AuthState,\n BetterAuthClientConfig,\n BetterAuthClientReturn,\n StorageKeys,\n} from './createAuthClient'\n\n// re-export useful better-auth types\nexport type { BetterAuthClientOptions, BetterAuthClientPlugin } from 'better-auth/client'\nexport type { Session, User } from 'better-auth'\n"
|
|
9
|
+
],
|
|
10
|
+
"version": 3
|
|
11
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Better-auth helpers for React / React Native applications
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - JWT support (for Zero, Tauri, React Native)
|
|
6
|
+
* - Session persistence in local storage
|
|
7
|
+
* - Token validation and refresh
|
|
8
|
+
* - State management with emitters
|
|
9
|
+
* - Automatic retry on errors
|
|
10
|
+
*/
|
|
11
|
+
import { createEmitter, type Emitter } from "@take-out/helpers";
|
|
12
|
+
import type { Session, User } from "better-auth";
|
|
13
|
+
import { type BetterAuthClientOptions, createAuthClient } from "better-auth/client";
|
|
14
|
+
export interface StorageKeys {
|
|
15
|
+
token: string;
|
|
16
|
+
session: string;
|
|
17
|
+
}
|
|
18
|
+
export type AuthState<U extends User = User> = {
|
|
19
|
+
state: "loading" | "logged-in" | "logged-out";
|
|
20
|
+
session: Session | null;
|
|
21
|
+
user: U | null;
|
|
22
|
+
token: string | null;
|
|
23
|
+
};
|
|
24
|
+
export interface BetterAuthClientProps<TUser extends User = User> extends BetterAuthClientOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Callback to transform and type the user object
|
|
27
|
+
* This allows you to add app-specific fields and ensure proper typing
|
|
28
|
+
* @default (user) => user
|
|
29
|
+
*/
|
|
30
|
+
createUser?: (user: User) => TUser;
|
|
31
|
+
/**
|
|
32
|
+
* Optional callback when authentication state changes
|
|
33
|
+
*/
|
|
34
|
+
onAuthStateChange?: (state: AuthState<TUser>) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Optional callback for handling auth errors
|
|
37
|
+
*/
|
|
38
|
+
onAuthError?: (error: any) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Storage key prefix for local storage
|
|
41
|
+
* @default 'auth'
|
|
42
|
+
*/
|
|
43
|
+
storagePrefix?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Retry delay in milliseconds after auth errors
|
|
46
|
+
* @default 4000
|
|
47
|
+
*/
|
|
48
|
+
retryDelay?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Custom token validation endpoint
|
|
51
|
+
* @default '/api/auth/validateToken'
|
|
52
|
+
*/
|
|
53
|
+
tokenValidationEndpoint?: string;
|
|
54
|
+
}
|
|
55
|
+
export interface BetterAuthClientReturn<
|
|
56
|
+
U extends User = User,
|
|
57
|
+
TClient = any
|
|
58
|
+
> {
|
|
59
|
+
clearState: () => void;
|
|
60
|
+
authState: ReturnType<typeof createEmitter<AuthState<U>>>;
|
|
61
|
+
authClient: TClient;
|
|
62
|
+
setAuthClientToken: (props: {
|
|
63
|
+
token: string;
|
|
64
|
+
session: string;
|
|
65
|
+
}) => Promise<void>;
|
|
66
|
+
clearAuthClientToken: () => void;
|
|
67
|
+
useAuth: () => AuthState<U>;
|
|
68
|
+
getAuth: () => AuthState<U> & {
|
|
69
|
+
loggedIn: boolean;
|
|
70
|
+
};
|
|
71
|
+
getValidToken: () => Promise<string | undefined>;
|
|
72
|
+
updateAuthClient: (session: string) => void;
|
|
73
|
+
authClientVersion: Emitter<number>;
|
|
74
|
+
}
|
|
75
|
+
type InferUser<T> = T extends {
|
|
76
|
+
createUser?: (user: User) => infer R;
|
|
77
|
+
} ? R extends User ? R : User : User;
|
|
78
|
+
export declare function createBetterAuthClient<const Opts extends BetterAuthClientProps<any>>(options: Opts): BetterAuthClientReturn<InferUser<Opts>, ReturnType<typeof createAuthClient<Opts>>>;
|
|
79
|
+
export {};
|
|
80
|
+
|
|
81
|
+
//# sourceMappingURL=createAuthClient.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mappings": ";;;;;;;;;;AAWA,SACE,oBAEK,eAGA,mBAAmB;AAC1B,cAAc,SAAS,YAAY,aAAa;AAChD,cAAc,yBAAyB,wBAAwB,oBAAoB;AAEnF,iBAAiB,YAAY;CAC3B;CACA;AACD;AAED,YAAY,UAAU,UAAU,OAAO,QAAQ;CAC7C,OAAO,YAAY,cAAc;CACjC,SAAS;CACT,MAAM;CACN;AACD;AAED,iBAAiB,sBAAsB,cAAc,OAAO,cAClD,wBAAwB;;;;;;CAMhC,cAAcA,MAAM,SAAS;;;;CAI7B,qBAAqBC,OAAO,UAAU;;;;CAItC,eAAeC;;;;;CAKf;;;;;CAKA;;;;;CAKA;AACD;AAED,iBAAiB;CAAuB,UAAU,OAAO;CAAM;EAAe;CAC5E;CACA,WAAW,kBAAkB,cAAc,UAAU;CACrD,YAAY;CACZ,qBAAqBC,OAAO;EAAE;EAAe;CAAiB,MAAK;CACnE;CACA,eAAe,UAAU;CACzB,eAAe,UAAU,KAAK;EAAE;CAAmB;CACnD,qBAAqB;CACrB,mBAAmBC;CACnB,mBAAmB;AACpB;KAEI,UAAU,KAAK,UAAU;CAAE,cAAcJ,MAAM,eAAe;AAAG,IAClE,UAAU,OACR,IACA,OACF;AAEJ,OAAO,iBAAS,6BAA6B,aAAa,4BACxDK,SAAS,OACR,uBAAuB,UAAU,OAAO,kBAAkB,iBAAiB",
|
|
3
|
+
"names": [
|
|
4
|
+
"user: User",
|
|
5
|
+
"state: AuthState<TUser>",
|
|
6
|
+
"error: any",
|
|
7
|
+
"props: { token: string; session: string }",
|
|
8
|
+
"session: string",
|
|
9
|
+
"options: Opts"
|
|
10
|
+
],
|
|
11
|
+
"sources": [
|
|
12
|
+
"src/createAuthClient.ts"
|
|
13
|
+
],
|
|
14
|
+
"sourcesContent": [
|
|
15
|
+
"/**\n * Better-auth helpers for React / React Native applications\n *\n * Features:\n * - JWT support (for Zero, Tauri, React Native)\n * - Session persistence in local storage\n * - Token validation and refresh\n * - State management with emitters\n * - Automatic retry on errors\n */\n\nimport {\n createEmitter,\n createLocalStorageValue,\n type Emitter,\n isEqualDeepLite,\n useEmitterValue,\n} from '@take-out/helpers'\nimport type { Session, User } from 'better-auth'\nimport { type BetterAuthClientOptions, createAuthClient } from 'better-auth/client'\n\nexport interface StorageKeys {\n token: string\n session: string\n}\n\nexport type AuthState<U extends User = User> = {\n state: 'loading' | 'logged-in' | 'logged-out'\n session: Session | null\n user: U | null\n token: string | null\n}\n\nexport interface BetterAuthClientProps<TUser extends User = User>\n extends BetterAuthClientOptions {\n /**\n * Callback to transform and type the user object\n * This allows you to add app-specific fields and ensure proper typing\n * @default (user) => user\n */\n createUser?: (user: User) => TUser\n /**\n * Optional callback when authentication state changes\n */\n onAuthStateChange?: (state: AuthState<TUser>) => void\n /**\n * Optional callback for handling auth errors\n */\n onAuthError?: (error: any) => void\n /**\n * Storage key prefix for local storage\n * @default 'auth'\n */\n storagePrefix?: string\n /**\n * Retry delay in milliseconds after auth errors\n * @default 4000\n */\n retryDelay?: number\n /**\n * Custom token validation endpoint\n * @default '/api/auth/validateToken'\n */\n tokenValidationEndpoint?: string\n}\n\nexport interface BetterAuthClientReturn<U extends User = User, TClient = any> {\n clearState: () => void\n authState: ReturnType<typeof createEmitter<AuthState<U>>>\n authClient: TClient\n setAuthClientToken: (props: { token: string; session: string }) => Promise<void>\n clearAuthClientToken: () => void\n useAuth: () => AuthState<U>\n getAuth: () => AuthState<U> & { loggedIn: boolean }\n getValidToken: () => Promise<string | undefined>\n updateAuthClient: (session: string) => void\n authClientVersion: Emitter<number>\n}\n\ntype InferUser<T> = T extends { createUser?: (user: User) => infer R }\n ? R extends User\n ? R\n : User\n : User\n\nexport function createBetterAuthClient<const Opts extends BetterAuthClientProps<any>>(\n options: Opts\n): BetterAuthClientReturn<InferUser<Opts>, ReturnType<typeof createAuthClient<Opts>>> {\n type TUser = InferUser<Opts>\n const {\n onAuthStateChange,\n onAuthError,\n createUser,\n storagePrefix = 'auth',\n retryDelay = 4000,\n tokenValidationEndpoint = '/api/auth/validateToken',\n ...authClientOptions\n } = options\n\n const empty: AuthState<TUser> = {\n state: 'logged-out',\n session: null,\n user: null,\n token: null,\n }\n\n const createAuthClientWithSession = (session: string) => {\n return createAuthClient({\n ...authClientOptions,\n fetchOptions: {\n headers: {\n Authorization: `Bearer ${session}`,\n },\n },\n })\n }\n\n const keysStorage = createLocalStorageValue<StorageKeys>(`${storagePrefix}-keys`)\n const stateStorage = createLocalStorageValue<AuthState<TUser>>(`${storagePrefix}-state`)\n\n let authClient = (() => {\n const existingSession = keysStorage.get()?.session\n return existingSession\n ? createAuthClientWithSession(existingSession)\n : createAuthClient(authClientOptions as Opts)\n })()\n\n const authState = createEmitter<AuthState<TUser>>(\n 'authState',\n stateStorage.get() || empty,\n {\n comparator: isEqualDeepLite,\n }\n )\n\n const authClientVersion = createEmitter<number>('authClientVersion', 0)\n\n const setState = (update: Partial<AuthState<TUser>>) => {\n const current = authState.value!\n const next = { ...current, ...update }\n stateStorage.set(next)\n authState.emit(next)\n\n // update storage keys\n if (next.token && next.session) {\n keysStorage.set({\n token: next.token,\n session: next.session.token,\n })\n } else {\n keysStorage.set({\n token: '',\n session: '',\n })\n }\n\n // call optional callback\n onAuthStateChange?.(next)\n }\n\n const setAuthClientToken = async (props: { token: string; session: string }) => {\n keysStorage.set(props)\n updateAuthClient(props.session)\n }\n\n function updateAuthClient(session: string) {\n authClient = createAuthClientWithSession(session)\n authClientVersion.emit(Math.random())\n subscribeToAuthEffect()\n }\n\n let dispose: Function | null = null\n let retryTimer: ReturnType<typeof setTimeout> | null = null\n\n function subscribeToAuthEffect() {\n dispose?.()\n\n dispose = authClient.useSession.subscribe(async (props) => {\n const { data: dataGeneric, isPending, error } = props\n\n if (error) {\n onAuthError?.(error)\n // retry by re-subscribing after a delay to recover from transient errors\n scheduleAuthRetry(retryDelay)\n return\n }\n\n const data = dataGeneric as\n | undefined\n | {\n session?: AuthState<TUser>['session']\n user?: AuthState<TUser>['user']\n }\n\n setState({\n state: isPending ? 'loading' : data?.session ? 'logged-in' : 'logged-out',\n session: data?.session,\n user: data?.user\n ? createUser\n ? createUser(data.user as User)\n : (data.user as any)\n : null,\n })\n\n // get token on creating a new session (for native + tauri)\n if (data?.session && !authState.value.token) {\n getValidToken().then((token) => {\n if (token) {\n setState({ token })\n }\n })\n }\n })\n }\n\n function scheduleAuthRetry(delayMs: number) {\n if (retryTimer) {\n clearTimeout(retryTimer)\n }\n retryTimer = setTimeout(() => {\n retryTimer = null\n subscribeToAuthEffect()\n }, delayMs)\n }\n\n async function getValidToken(): Promise<string | undefined> {\n const existing = keysStorage.get()?.token\n\n if (existing) {\n try {\n const response = await fetch(tokenValidationEndpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n token: existing,\n }),\n }).then((res) => res.json())\n\n if (response?.valid) {\n return existing\n }\n } catch (error) {\n console.error('Error validating token:', error)\n }\n }\n\n const res = await authClient.$fetch('/token')\n if (res.error) {\n console.error(`Error fetching token: ${res.error.statusText}`)\n return undefined\n }\n const data = res.data as any\n return data?.token as string | undefined\n }\n\n const clearAuthClientToken = () => {\n keysStorage.remove()\n }\n\n const getAuth = () => {\n const state = authState?.value || empty\n return {\n ...state,\n loggedIn: !!state.session,\n }\n }\n\n const useAuth = () => {\n return useEmitterValue(authState) || empty\n }\n\n function clearState() {\n keysStorage.clear()\n stateStorage.clear()\n setState(empty)\n }\n\n // initialize subscription\n subscribeToAuthEffect()\n\n // cleanup on unmount\n if (typeof window !== 'undefined' && window.addEventListener) {\n const cleanup = () => {\n dispose?.()\n if (retryTimer) {\n clearTimeout(retryTimer)\n }\n }\n window.addEventListener('beforeunload', cleanup)\n }\n\n const proxiedAuthClient = new Proxy(authClient, {\n get(_target, key) {\n if (key === 'signOut') {\n return async () => {\n clearState()\n // @ts-expect-error better-auth type issue, signOut does exist\n await authClient.signOut?.()\n // force refresh to clear any remaining state\n if (typeof window !== 'undefined') {\n window.location?.reload?.()\n }\n }\n }\n // ensure always latest authClient after login\n return Reflect.get(authClient, key)\n },\n }) as ReturnType<typeof createAuthClient<Opts>>\n\n return {\n authClientVersion,\n clearState,\n authState,\n authClient: proxiedAuthClient,\n setAuthClientToken,\n clearAuthClientToken,\n useAuth,\n getAuth,\n getValidToken,\n updateAuthClient,\n }\n}\n"
|
|
16
|
+
],
|
|
17
|
+
"version": 3
|
|
18
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Session } from "better-auth";
|
|
2
|
+
import type { BetterAuthClientReturn } from "../createAuthClient";
|
|
3
|
+
/**
|
|
4
|
+
* Hook to access just the auth session
|
|
5
|
+
*/
|
|
6
|
+
export declare function useAuthSession<U = any>(client: BetterAuthClientReturn<U>): Session | null;
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=useAuthSession.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mappings": "AAAA,cAAc,eAAe,aAAa;AAC1C,cAAc,8BAA8B,qBAAqB;;;;AAKjE,OAAO,iBAAS,eAAe,SAC7BA,QAAQ,uBAAuB,KAC9B",
|
|
3
|
+
"names": [
|
|
4
|
+
"client: BetterAuthClientReturn<U>"
|
|
5
|
+
],
|
|
6
|
+
"sources": [
|
|
7
|
+
"src/hooks/useAuthSession.ts"
|
|
8
|
+
],
|
|
9
|
+
"sourcesContent": [
|
|
10
|
+
"import type { Session } from 'better-auth'\nimport type { BetterAuthClientReturn } from '../createAuthClient'\n\n/**\n * Hook to access just the auth session\n */\nexport function useAuthSession<U = any>(\n client: BetterAuthClientReturn<U>\n): Session | null {\n const state = client.useAuth()\n return state.session\n}"
|
|
11
|
+
],
|
|
12
|
+
"version": 3
|
|
13
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AuthState, BetterAuthClientReturn } from "../createAuthClient";
|
|
2
|
+
/**
|
|
3
|
+
* Hook to access the full auth state
|
|
4
|
+
*/
|
|
5
|
+
export declare function useAuthState<U = any>(client: BetterAuthClientReturn<U>): AuthState<U>;
|
|
6
|
+
|
|
7
|
+
//# sourceMappingURL=useAuthState.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mappings": "AACA,cAAc,WAAW,8BAA8B,qBAAqB;;;;AAK5E,OAAO,iBAAS,aAAa,SAC3BA,QAAQ,uBAAuB,KAC9B,UAAU",
|
|
3
|
+
"names": [
|
|
4
|
+
"client: BetterAuthClientReturn<U>"
|
|
5
|
+
],
|
|
6
|
+
"sources": [
|
|
7
|
+
"src/hooks/useAuthState.ts"
|
|
8
|
+
],
|
|
9
|
+
"sourcesContent": [
|
|
10
|
+
"import { useEmitterValue } from '@vxrn/helpers'\nimport type { AuthState, BetterAuthClientReturn } from '../createAuthClient'\n\n/**\n * Hook to access the full auth state\n */\nexport function useAuthState<U = any>(\n client: BetterAuthClientReturn<U>\n): AuthState<U> {\n return client.useAuth()\n}"
|
|
11
|
+
],
|
|
12
|
+
"version": 3
|
|
13
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { User } from "better-auth";
|
|
2
|
+
import type { BetterAuthClientReturn } from "../createAuthClient";
|
|
3
|
+
/**
|
|
4
|
+
* Hook to access just the authenticated user
|
|
5
|
+
*/
|
|
6
|
+
export declare function useAuthUser<U extends User = User>(client: BetterAuthClientReturn<U>): U | null;
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=useAuthUser.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mappings": "AAAA,cAAc,YAAY,aAAa;AACvC,cAAc,8BAA8B,qBAAqB;;;;AAKjE,OAAO,iBAAS,YAAY,UAAU,OAAO,MAC3CA,QAAQ,uBAAuB,KAC9B",
|
|
3
|
+
"names": [
|
|
4
|
+
"client: BetterAuthClientReturn<U>"
|
|
5
|
+
],
|
|
6
|
+
"sources": [
|
|
7
|
+
"src/hooks/useAuthUser.ts"
|
|
8
|
+
],
|
|
9
|
+
"sourcesContent": [
|
|
10
|
+
"import type { User } from 'better-auth'\nimport type { BetterAuthClientReturn } from '../createAuthClient'\n\n/**\n * Hook to access just the authenticated user\n */\nexport function useAuthUser<U extends User = User>(\n client: BetterAuthClientReturn<U>\n): U | null {\n const state = client.useAuth()\n return state.user\n}"
|
|
11
|
+
],
|
|
12
|
+
"version": 3
|
|
13
|
+
}
|
package/types/index.d.ts
ADDED