@ram_28/kf-ai-sdk 1.0.2 → 1.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ram_28/kf-ai-sdk",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Type-safe, AI-driven SDK for building modern web applications with role-based access control",
5
5
  "author": "Ramprasad",
6
6
  "license": "MIT",
@@ -89,7 +89,8 @@ export function AuthProvider({
89
89
  retryDelay: authConfig.retry.delay,
90
90
  staleTime: authConfig.staleTime,
91
91
  gcTime: authConfig.staleTime * 2,
92
- refetchOnWindowFocus: true,
92
+ refetchOnWindowFocus: authConfig.refetchOnWindowFocus ?? true,
93
+ refetchOnReconnect: authConfig.refetchOnReconnect ?? true,
93
94
  refetchInterval: authConfig.sessionCheckInterval || false,
94
95
  });
95
96
 
@@ -167,6 +168,11 @@ export function AuthProvider({
167
168
  );
168
169
 
169
170
  const refreshSession = useCallback(async (): Promise<SessionResponse | null> => {
171
+ // Prevent concurrent refreshes - return existing data if already fetching
172
+ if (isFetching) {
173
+ return sessionData || null;
174
+ }
175
+
170
176
  try {
171
177
  const result = await refetch();
172
178
  return result.data || null;
@@ -174,7 +180,7 @@ export function AuthProvider({
174
180
  setError(err as Error);
175
181
  return null;
176
182
  }
177
- }, [refetch]);
183
+ }, [refetch, isFetching, sessionData]);
178
184
 
179
185
  const hasRole = useCallback(
180
186
  (role: string): boolean => {
@@ -61,38 +61,86 @@ export async function fetchSession(): Promise<SessionResponse> {
61
61
  }
62
62
 
63
63
  /**
64
- * Initiate login flow by redirecting to the auth provider
65
- * The server handles the OAuth flow and sets cookies
64
+ * Initiates OAuth login flow by redirecting to the auth provider.
65
+ *
66
+ * @remarks
67
+ * This function redirects the browser and never resolves.
68
+ * Any code after calling this function will not execute.
69
+ *
70
+ * @param provider - OAuth provider to use (defaults to config.defaultProvider)
71
+ * @param options - Login options including callback URL and custom params
72
+ * @returns Promise that never resolves (browser redirects away)
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * // Correct usage - no code after login()
77
+ * function handleLoginClick() {
78
+ * login('google');
79
+ * // Don't put code here - it won't run
80
+ * }
81
+ * ```
66
82
  */
67
83
  export function initiateLogin(
68
84
  provider?: AuthProviderName,
69
85
  options?: LoginOptions
70
- ): void {
71
- const config = getAuthConfig();
72
- const baseUrl = getAuthBaseUrl();
73
- const selectedProvider = provider || config.defaultProvider;
86
+ ): Promise<never> {
87
+ return new Promise(() => {
88
+ const config = getAuthConfig();
89
+ const baseUrl = getAuthBaseUrl();
90
+
91
+ // Validate base URL
92
+ if (!baseUrl) {
93
+ throw new Error(
94
+ 'Auth base URL is not configured. Call setApiBaseUrl("https://...") or configureAuth({ baseUrl: "https://..." }) first.'
95
+ );
96
+ }
74
97
 
75
- const providerConfig = getProviderConfig(selectedProvider);
76
- if (!providerConfig) {
77
- throw new Error(`Auth provider "${selectedProvider}" is not configured`);
78
- }
98
+ const selectedProvider = provider || config.defaultProvider;
99
+ const providerConfig = getProviderConfig(selectedProvider);
79
100
 
80
- const loginUrl = new URL(`${baseUrl}${providerConfig.loginPath}`);
101
+ // Validate provider config
102
+ if (!providerConfig) {
103
+ const availableProviders = Object.keys(config.providers || {}).join(", ") || "none";
104
+ throw new Error(
105
+ `Auth provider "${selectedProvider}" is not configured. Available providers: ${availableProviders}`
106
+ );
107
+ }
81
108
 
82
- if (options?.callbackUrl || config.callbackUrl) {
83
- loginUrl.searchParams.set(
84
- "callbackUrl",
85
- options?.callbackUrl || config.callbackUrl || window.location.href
86
- );
87
- }
109
+ // Validate login path
110
+ if (!providerConfig.loginPath) {
111
+ throw new Error(
112
+ `Login path not configured for provider "${selectedProvider}". ` +
113
+ `Configure it with: configureAuth({ providers: { ${selectedProvider}: { loginPath: '/api/auth/...' } } })`
114
+ );
115
+ }
88
116
 
89
- if (options?.params) {
90
- Object.entries(options.params).forEach(([key, value]) => {
91
- loginUrl.searchParams.set(key, value);
92
- });
93
- }
117
+ // Validate URL construction
118
+ let loginUrl: URL;
119
+ try {
120
+ loginUrl = new URL(`${baseUrl}${providerConfig.loginPath}`);
121
+ } catch {
122
+ throw new Error(
123
+ `Failed to construct login URL. Base URL: "${baseUrl}", Login path: "${providerConfig.loginPath}". ` +
124
+ `Ensure baseUrl is a valid URL (e.g., "https://example.com").`
125
+ );
126
+ }
127
+
128
+ if (options?.callbackUrl || config.callbackUrl) {
129
+ loginUrl.searchParams.set(
130
+ "callbackUrl",
131
+ options?.callbackUrl || config.callbackUrl || window.location.href
132
+ );
133
+ }
94
134
 
95
- window.location.href = loginUrl.toString();
135
+ if (options?.params) {
136
+ Object.entries(options.params).forEach(([key, value]) => {
137
+ loginUrl.searchParams.set(key, value);
138
+ });
139
+ }
140
+
141
+ window.location.href = loginUrl.toString();
142
+ // Promise never resolves - browser navigates away
143
+ });
96
144
  }
97
145
 
98
146
  /**
@@ -25,6 +25,8 @@ const defaultAuthConfig: AuthConfig = {
25
25
  delay: 1000,
26
26
  },
27
27
  staleTime: 5 * 60 * 1000,
28
+ refetchOnWindowFocus: true,
29
+ refetchOnReconnect: true,
28
30
  };
29
31
 
30
32
  /**
package/sdk/auth/types.ts CHANGED
@@ -79,6 +79,12 @@ export interface AuthConfig {
79
79
 
80
80
  /** React Query stale time for session data */
81
81
  staleTime: number;
82
+
83
+ /** Refetch session when window regains focus (default: true) */
84
+ refetchOnWindowFocus?: boolean;
85
+
86
+ /** Refetch session when network reconnects (default: true) */
87
+ refetchOnReconnect?: boolean;
82
88
  }
83
89
 
84
90
  /**
@@ -3,6 +3,7 @@
3
3
  // ============================================================
4
4
  // Main hook for consuming authentication state
5
5
 
6
+ import { useMemo } from "react";
6
7
  import type { UseAuthReturn } from "./types";
7
8
  import { useAuthContext } from "./AuthProvider";
8
9
 
@@ -33,35 +34,36 @@ import { useAuthContext } from "./AuthProvider";
33
34
  export function useAuth(): UseAuthReturn {
34
35
  const context = useAuthContext();
35
36
 
36
- const {
37
- user,
38
- staticBaseUrl,
39
- buildId,
40
- status,
41
- isAuthenticated,
42
- isLoading,
43
- login,
44
- logout,
45
- refreshSession,
46
- hasRole,
47
- hasAnyRole,
48
- error,
49
- clearError,
50
- } = context;
51
-
52
- return {
53
- user,
54
- staticBaseUrl,
55
- buildId,
56
- status,
57
- isAuthenticated,
58
- isLoading,
59
- login,
60
- logout,
61
- refreshSession,
62
- hasRole,
63
- hasAnyRole,
64
- error,
65
- clearError,
66
- };
37
+ return useMemo(
38
+ () => ({
39
+ user: context.user,
40
+ staticBaseUrl: context.staticBaseUrl,
41
+ buildId: context.buildId,
42
+ status: context.status,
43
+ isAuthenticated: context.isAuthenticated,
44
+ isLoading: context.isLoading,
45
+ login: context.login,
46
+ logout: context.logout,
47
+ refreshSession: context.refreshSession,
48
+ hasRole: context.hasRole,
49
+ hasAnyRole: context.hasAnyRole,
50
+ error: context.error,
51
+ clearError: context.clearError,
52
+ }),
53
+ [
54
+ context.user,
55
+ context.staticBaseUrl,
56
+ context.buildId,
57
+ context.status,
58
+ context.isAuthenticated,
59
+ context.isLoading,
60
+ context.login,
61
+ context.logout,
62
+ context.refreshSession,
63
+ context.hasRole,
64
+ context.hasAnyRole,
65
+ context.error,
66
+ context.clearError,
67
+ ]
68
+ );
67
69
  }