hightjs 0.1.1

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 (131) hide show
  1. package/.idea/HightJS.iml +9 -0
  2. package/.idea/copilot.data.migration.agent.xml +6 -0
  3. package/.idea/copilot.data.migration.ask.xml +6 -0
  4. package/.idea/copilot.data.migration.ask2agent.xml +6 -0
  5. package/.idea/copilot.data.migration.edit.xml +6 -0
  6. package/.idea/inspectionProfiles/Project_Default.xml +13 -0
  7. package/.idea/libraries/test_package.xml +9 -0
  8. package/.idea/libraries/ts_commonjs_default_export.xml +9 -0
  9. package/.idea/misc.xml +7 -0
  10. package/.idea/modules.xml +8 -0
  11. package/.idea/vcs.xml +6 -0
  12. package/LICENSE +13 -0
  13. package/README.md +508 -0
  14. package/dist/adapters/express.d.ts +7 -0
  15. package/dist/adapters/express.js +63 -0
  16. package/dist/adapters/factory.d.ts +23 -0
  17. package/dist/adapters/factory.js +122 -0
  18. package/dist/adapters/fastify.d.ts +25 -0
  19. package/dist/adapters/fastify.js +61 -0
  20. package/dist/adapters/native.d.ts +8 -0
  21. package/dist/adapters/native.js +203 -0
  22. package/dist/adapters/starters/express.d.ts +0 -0
  23. package/dist/adapters/starters/express.js +1 -0
  24. package/dist/adapters/starters/factory.d.ts +0 -0
  25. package/dist/adapters/starters/factory.js +1 -0
  26. package/dist/adapters/starters/fastify.d.ts +0 -0
  27. package/dist/adapters/starters/fastify.js +1 -0
  28. package/dist/adapters/starters/index.d.ts +0 -0
  29. package/dist/adapters/starters/index.js +1 -0
  30. package/dist/adapters/starters/native.d.ts +0 -0
  31. package/dist/adapters/starters/native.js +1 -0
  32. package/dist/api/console.d.ts +92 -0
  33. package/dist/api/console.js +276 -0
  34. package/dist/api/http.d.ts +180 -0
  35. package/dist/api/http.js +467 -0
  36. package/dist/auth/client.d.ts +14 -0
  37. package/dist/auth/client.js +68 -0
  38. package/dist/auth/components.d.ts +29 -0
  39. package/dist/auth/components.js +84 -0
  40. package/dist/auth/core.d.ts +38 -0
  41. package/dist/auth/core.js +124 -0
  42. package/dist/auth/index.d.ts +7 -0
  43. package/dist/auth/index.js +27 -0
  44. package/dist/auth/jwt.d.ts +41 -0
  45. package/dist/auth/jwt.js +169 -0
  46. package/dist/auth/providers.d.ts +5 -0
  47. package/dist/auth/providers.js +14 -0
  48. package/dist/auth/react/index.d.ts +6 -0
  49. package/dist/auth/react/index.js +32 -0
  50. package/dist/auth/react.d.ts +22 -0
  51. package/dist/auth/react.js +175 -0
  52. package/dist/auth/routes.d.ts +16 -0
  53. package/dist/auth/routes.js +104 -0
  54. package/dist/auth/types.d.ts +62 -0
  55. package/dist/auth/types.js +2 -0
  56. package/dist/bin/hightjs.d.ts +2 -0
  57. package/dist/bin/hightjs.js +35 -0
  58. package/dist/builder.d.ts +32 -0
  59. package/dist/builder.js +341 -0
  60. package/dist/client/DefaultNotFound.d.ts +1 -0
  61. package/dist/client/DefaultNotFound.js +53 -0
  62. package/dist/client/ErrorBoundary.d.ts +16 -0
  63. package/dist/client/ErrorBoundary.js +181 -0
  64. package/dist/client/clientRouter.d.ts +58 -0
  65. package/dist/client/clientRouter.js +116 -0
  66. package/dist/client/entry.client.d.ts +1 -0
  67. package/dist/client/entry.client.js +271 -0
  68. package/dist/client/routerContext.d.ts +26 -0
  69. package/dist/client/routerContext.js +62 -0
  70. package/dist/client.d.ts +3 -0
  71. package/dist/client.js +8 -0
  72. package/dist/components/Link.d.ts +7 -0
  73. package/dist/components/Link.js +13 -0
  74. package/dist/eslint/index.d.ts +32 -0
  75. package/dist/eslint/index.js +15 -0
  76. package/dist/eslint/use-client-rule.d.ts +19 -0
  77. package/dist/eslint/use-client-rule.js +99 -0
  78. package/dist/eslintSetup.d.ts +0 -0
  79. package/dist/eslintSetup.js +1 -0
  80. package/dist/example/src/web/routes/index.d.ts +3 -0
  81. package/dist/example/src/web/routes/index.js +15 -0
  82. package/dist/helpers.d.ts +18 -0
  83. package/dist/helpers.js +318 -0
  84. package/dist/hotReload.d.ts +23 -0
  85. package/dist/hotReload.js +292 -0
  86. package/dist/index.d.ts +17 -0
  87. package/dist/index.js +480 -0
  88. package/dist/renderer.d.ts +14 -0
  89. package/dist/renderer.js +106 -0
  90. package/dist/router.d.ts +78 -0
  91. package/dist/router.js +359 -0
  92. package/dist/types/framework.d.ts +37 -0
  93. package/dist/types/framework.js +2 -0
  94. package/dist/types.d.ts +43 -0
  95. package/dist/types.js +2 -0
  96. package/dist/typescript/use-client-plugin.d.ts +5 -0
  97. package/dist/typescript/use-client-plugin.js +113 -0
  98. package/dist/validation.d.ts +0 -0
  99. package/dist/validation.js +1 -0
  100. package/package.json +72 -0
  101. package/src/adapters/express.ts +70 -0
  102. package/src/adapters/factory.ts +96 -0
  103. package/src/adapters/fastify.ts +88 -0
  104. package/src/adapters/native.ts +223 -0
  105. package/src/api/console.ts +285 -0
  106. package/src/api/http.ts +515 -0
  107. package/src/auth/client.ts +74 -0
  108. package/src/auth/components.tsx +109 -0
  109. package/src/auth/core.ts +143 -0
  110. package/src/auth/index.ts +9 -0
  111. package/src/auth/jwt.ts +194 -0
  112. package/src/auth/providers.ts +13 -0
  113. package/src/auth/react/index.ts +9 -0
  114. package/src/auth/react.tsx +209 -0
  115. package/src/auth/routes.ts +133 -0
  116. package/src/auth/types.ts +73 -0
  117. package/src/bin/hightjs.js +40 -0
  118. package/src/builder.js +362 -0
  119. package/src/client/DefaultNotFound.tsx +68 -0
  120. package/src/client/clientRouter.ts +137 -0
  121. package/src/client/entry.client.tsx +302 -0
  122. package/src/client.ts +8 -0
  123. package/src/components/Link.tsx +22 -0
  124. package/src/helpers.ts +316 -0
  125. package/src/hotReload.ts +289 -0
  126. package/src/index.ts +514 -0
  127. package/src/renderer.tsx +122 -0
  128. package/src/router.ts +400 -0
  129. package/src/types/framework.ts +42 -0
  130. package/src/types.ts +54 -0
  131. package/tsconfig.json +17 -0
@@ -0,0 +1,143 @@
1
+ import { HightJSRequest, HightJSResponse } from '../api/http';
2
+ import type { AuthConfig, AuthProvider, User, Session } from './types';
3
+ import { SessionManager } from './jwt';
4
+
5
+ export class HWebAuth {
6
+ private config: AuthConfig;
7
+ private sessionManager: SessionManager;
8
+
9
+ constructor(config: AuthConfig) {
10
+ this.config = {
11
+ session: { strategy: 'jwt', maxAge: 86400, ...config.session },
12
+ pages: { signIn: '/auth/signin', signOut: '/auth/signout', ...config.pages },
13
+ ...config
14
+ };
15
+
16
+ this.sessionManager = new SessionManager(
17
+ config.secret,
18
+ this.config.session?.maxAge || 86400
19
+ );
20
+ }
21
+
22
+ /**
23
+ * Middleware para adicionar autenticação às rotas
24
+ */
25
+ private async middleware(req: HightJSRequest): Promise<{ session: Session | null; user: User | null }> {
26
+ const token = this.getTokenFromRequest(req);
27
+
28
+ if (!token) {
29
+ return { session: null, user: null };
30
+ }
31
+
32
+ const session = this.sessionManager.verifySession(token);
33
+ return {
34
+ session,
35
+ user: session?.user || null
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Autentica um usuário com credenciais
41
+ */
42
+ async signIn(provider: string, credentials: Record<string, string>): Promise<{ session: Session; token: string } | null> {
43
+ const authProvider = this.config.providers.find(p => p.id === provider);
44
+ if (!authProvider || authProvider.type !== 'credentials') {
45
+ return null;
46
+ }
47
+
48
+ if (!authProvider.authorize) {
49
+ return null;
50
+ }
51
+
52
+ try {
53
+ const user = await authProvider.authorize(credentials);
54
+ if (!user) return null;
55
+
56
+ // Callback de signIn se definido
57
+ if (this.config.callbacks?.signIn) {
58
+ const allowed = await this.config.callbacks.signIn(user, { provider }, {});
59
+ if (!allowed) return null;
60
+ }
61
+
62
+ const result = this.sessionManager.createSession(user);
63
+
64
+ // Callback de sessão se definido
65
+ if (this.config.callbacks?.session) {
66
+ result.session = await this.config.callbacks.session(result.session, user);
67
+ }
68
+
69
+ return result;
70
+ } catch (error) {
71
+ console.error('[hweb-auth] Erro no signIn:', error);
72
+ return null;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Faz logout do usuário
78
+ */
79
+ signOut(): HightJSResponse {
80
+ return HightJSResponse
81
+ .json({ success: true })
82
+ .clearCookie('hweb-auth-token', {
83
+ path: '/',
84
+ httpOnly: true,
85
+ secure: true, // Always use secure cookies
86
+ sameSite: 'strict' // Stronger CSRF protection
87
+ });
88
+ }
89
+
90
+ /**
91
+ * Obtém a sessão atual
92
+ */
93
+ async getSession(req: HightJSRequest): Promise<Session | null> {
94
+ const { session } = await this.middleware(req);
95
+ return session;
96
+ }
97
+
98
+ /**
99
+ * Verifica se o usuário está autenticado
100
+ */
101
+ async isAuthenticated(req: HightJSRequest): Promise<boolean> {
102
+ const session = await this.getSession(req);
103
+ return session !== null;
104
+ }
105
+
106
+
107
+ /**
108
+ * Cria resposta com cookie de autenticação - Secure implementation
109
+ */
110
+ createAuthResponse(token: string, data: any): HightJSResponse {
111
+ return HightJSResponse
112
+ .json(data)
113
+ .cookie('hweb-auth-token', token, {
114
+ httpOnly: true,
115
+ secure: true, // Always secure, even in development
116
+ sameSite: 'strict', // Prevent CSRF attacks
117
+ maxAge: (this.config.session?.maxAge || 86400) * 1000,
118
+ path: '/',
119
+ domain: undefined // Let browser set automatically for security
120
+ })
121
+ .header('X-Content-Type-Options', 'nosniff')
122
+ .header('X-Frame-Options', 'DENY')
123
+ .header('X-XSS-Protection', '1; mode=block')
124
+ .header('Referrer-Policy', 'strict-origin-when-cross-origin');
125
+ }
126
+
127
+ /**
128
+ * Extrai token da requisição (cookie ou header)
129
+ */
130
+ private getTokenFromRequest(req: HightJSRequest): string | null {
131
+ // Primeiro tenta pegar do cookie
132
+ const cookieToken = req.cookie('hweb-auth-token');
133
+ if (cookieToken) return cookieToken;
134
+
135
+ // Depois tenta do header Authorization
136
+ const authHeader = req.header('authorization');
137
+ if (authHeader && typeof authHeader === 'string' && authHeader.startsWith('Bearer ')) {
138
+ return authHeader.substring(7);
139
+ }
140
+
141
+ return null;
142
+ }
143
+ }
@@ -0,0 +1,9 @@
1
+ // Exportações principais do sistema de autenticação
2
+ export * from './types';
3
+ export * from './providers';
4
+ export * from './core';
5
+ export * from './routes';
6
+ export * from './jwt';
7
+
8
+ export { CredentialsProvider } from './providers';
9
+ export { createAuthRoutes } from './routes';
@@ -0,0 +1,194 @@
1
+ import crypto from 'crypto';
2
+ import type { User, Session } from './types';
3
+
4
+ export class JWTManager {
5
+ private secret: string;
6
+
7
+ constructor(secret?: string) {
8
+ if (!secret && !process.env.HWEB_AUTH_SECRET) {
9
+ throw new Error('JWT secret is required. Set HWEB_AUTH_SECRET environment variable or provide secret parameter.');
10
+ }
11
+
12
+ this.secret = secret || process.env.HWEB_AUTH_SECRET!;
13
+
14
+ if (this.secret.length < 32) {
15
+ throw new Error('JWT secret must be at least 32 characters long for security.');
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Cria um JWT token com validação de algoritmo
21
+ */
22
+ sign(payload: any, expiresIn: number = 86400): string {
23
+ const header = { alg: 'HS256', typ: 'JWT' };
24
+ const now = Math.floor(Date.now() / 1000);
25
+
26
+ // Sanitize payload to prevent injection
27
+ const sanitizedPayload = this.sanitizePayload(payload);
28
+
29
+ const tokenPayload = {
30
+ ...sanitizedPayload,
31
+ iat: now,
32
+ exp: now + expiresIn,
33
+ alg: 'HS256' // Prevent algorithm confusion attacks
34
+ };
35
+
36
+ const encodedHeader = this.base64UrlEncode(JSON.stringify(header));
37
+ const encodedPayload = this.base64UrlEncode(JSON.stringify(tokenPayload));
38
+
39
+ const signature = this.createSignature(encodedHeader + '.' + encodedPayload);
40
+
41
+ return `${encodedHeader}.${encodedPayload}.${signature}`;
42
+ }
43
+
44
+ /**
45
+ * Verifica e decodifica um JWT token com validação rigorosa
46
+ */
47
+ verify(token: string): any | null {
48
+ try {
49
+ if (!token || typeof token !== 'string') return null;
50
+
51
+ const parts = token.split('.');
52
+ if (parts.length !== 3) return null;
53
+
54
+ const [headerEncoded, payloadEncoded, signature] = parts;
55
+
56
+ // Decode and validate header
57
+ const header = JSON.parse(this.base64UrlDecode(headerEncoded));
58
+ if (header.alg !== 'HS256' || header.typ !== 'JWT') {
59
+ return null; // Prevent algorithm confusion attacks
60
+ }
61
+
62
+ // Verifica a assinatura usando constant-time comparison
63
+ const expectedSignature = this.createSignature(headerEncoded + '.' + payloadEncoded);
64
+ if (!this.constantTimeEqual(signature, expectedSignature)) return null;
65
+
66
+ // Decodifica o payload
67
+ const decodedPayload = JSON.parse(this.base64UrlDecode(payloadEncoded));
68
+
69
+ // Validate algorithm in payload matches header
70
+ if (decodedPayload.alg !== 'HS256') return null;
71
+
72
+ // Verifica expiração com margem de erro de 30 segundos
73
+ const now = Math.floor(Date.now() / 1000);
74
+ if (decodedPayload.exp && decodedPayload.exp < (now - 30)) {
75
+ return null;
76
+ }
77
+
78
+ // Validate issued at time (not too far in future)
79
+ if (decodedPayload.iat && decodedPayload.iat > (now + 300)) {
80
+ return null;
81
+ }
82
+
83
+ return decodedPayload;
84
+ } catch (error) {
85
+ return null;
86
+ }
87
+ }
88
+
89
+ private sanitizePayload(payload: any): any {
90
+ if (typeof payload !== 'object' || payload === null) {
91
+ return {};
92
+ }
93
+
94
+ const sanitized: any = {};
95
+ for (const [key, value] of Object.entries(payload)) {
96
+ // Skip dangerous properties
97
+ if (key.startsWith('__') || key === 'constructor' || key === 'prototype') {
98
+ continue;
99
+ }
100
+ sanitized[key] = value;
101
+ }
102
+ return sanitized;
103
+ }
104
+
105
+ private constantTimeEqual(a: string, b: string): boolean {
106
+ if (a.length !== b.length) return false;
107
+
108
+ let result = 0;
109
+ for (let i = 0; i < a.length; i++) {
110
+ result |= a.charCodeAt(i) ^ b.charCodeAt(i);
111
+ }
112
+ return result === 0;
113
+ }
114
+
115
+ private base64UrlEncode(str: string): string {
116
+ return Buffer.from(str)
117
+ .toString('base64')
118
+ .replace(/\+/g, '-')
119
+ .replace(/\//g, '_')
120
+ .replace(/=/g, '');
121
+ }
122
+
123
+ private base64UrlDecode(str: string): string {
124
+ str += '='.repeat(4 - str.length % 4);
125
+ return Buffer.from(str.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString();
126
+ }
127
+
128
+ private createSignature(data: string): string {
129
+ return crypto
130
+ .createHmac('sha256', this.secret)
131
+ .update(data)
132
+ .digest('base64')
133
+ .replace(/\+/g, '-')
134
+ .replace(/\//g, '_')
135
+ .replace(/=/g, '');
136
+ }
137
+ }
138
+
139
+ export class SessionManager {
140
+ private jwtManager: JWTManager;
141
+ private maxAge: number;
142
+
143
+ constructor(secret?: string, maxAge: number = 86400) {
144
+ this.jwtManager = new JWTManager(secret);
145
+ this.maxAge = maxAge;
146
+ }
147
+
148
+ /**
149
+ * Cria uma nova sessão
150
+ */
151
+ createSession(user: User): { session: Session; token: string } {
152
+ const expires = new Date(Date.now() + this.maxAge * 1000).toISOString();
153
+
154
+ const session: Session = {
155
+ user,
156
+ expires
157
+ };
158
+
159
+ const token = this.jwtManager.sign({
160
+ ...user
161
+ }, this.maxAge);
162
+
163
+ return { session, token };
164
+ }
165
+
166
+ /**
167
+ * Verifica uma sessão a partir do token
168
+ */
169
+ verifySession(token: string): Session | null {
170
+ try {
171
+ const payload = this.jwtManager.verify(token);
172
+ if (!payload) return null;
173
+
174
+ const session: Session = {
175
+ user: payload,
176
+ expires: new Date(payload.exp * 1000).toISOString()
177
+ };
178
+
179
+ return session;
180
+ } catch (error) {
181
+ return null;
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Atualiza uma sessão existente
187
+ */
188
+ updateSession(token: string): { session: Session; token: string } | null {
189
+ const currentSession = this.verifySession(token);
190
+ if (!currentSession) return null;
191
+
192
+ return this.createSession(currentSession.user);
193
+ }
194
+ }
@@ -0,0 +1,13 @@
1
+ import type { AuthProvider, CredentialsConfig } from './types';
2
+
3
+ /**
4
+ * Provider para autenticação com credenciais (email/senha)
5
+ */
6
+ export function CredentialsProvider(config: CredentialsConfig): AuthProvider {
7
+ return {
8
+ id: config.id || 'credentials',
9
+ name: config.name || 'Credentials',
10
+ type: 'credentials',
11
+ authorize: config.authorize
12
+ };
13
+ }
@@ -0,0 +1,9 @@
1
+ // Exportações do frontend
2
+ export * from '../react';
3
+ export * from '../client';
4
+ export * from '../components';
5
+
6
+ // Re-exports das funções mais usadas para conveniência
7
+ export { getSession } from '../client';
8
+ export { useSession, useAuth, SessionProvider } from '../react';
9
+ export { ProtectedRoute, AuthGuard, GuestOnly } from '../components';
@@ -0,0 +1,209 @@
1
+ import React, { createContext, useContext, useEffect, useState, useCallback, ReactNode } from 'react';
2
+ import type { Session, SessionContextType, SignInOptions, SignInResult, User } from './types';
3
+ import {router} from "../client";
4
+
5
+ const SessionContext = createContext<SessionContextType | undefined>(undefined);
6
+
7
+ interface SessionProviderProps {
8
+ children: ReactNode;
9
+ basePath?: string;
10
+ refetchInterval?: number;
11
+ refetchOnWindowFocus?: boolean;
12
+ }
13
+
14
+ export function SessionProvider({
15
+ children,
16
+ basePath = '/api/auth',
17
+ refetchInterval = 0,
18
+ refetchOnWindowFocus = true
19
+ }: SessionProviderProps) {
20
+ const [session, setSession] = useState<Session | null>(null);
21
+ const [status, setStatus] = useState<'loading' | 'authenticated' | 'unauthenticated'>('loading');
22
+
23
+ // Fetch da sessão atual
24
+ const fetchSession = useCallback(async (): Promise<Session | null> => {
25
+ try {
26
+ const response = await fetch(`${basePath}/session`, {
27
+ credentials: 'include'
28
+ });
29
+
30
+ if (!response.ok) {
31
+ setStatus('unauthenticated');
32
+ return null;
33
+ }
34
+
35
+ const data = await response.json();
36
+ const sessionData = data.session;
37
+
38
+ if (sessionData) {
39
+ setSession(sessionData);
40
+ setStatus('authenticated');
41
+ return sessionData;
42
+ } else {
43
+ setSession(null);
44
+ setStatus('unauthenticated');
45
+ return null;
46
+ }
47
+ } catch (error) {
48
+ console.error('[hweb-auth] Erro ao buscar sessão:', error);
49
+ setSession(null);
50
+ setStatus('unauthenticated');
51
+ return null;
52
+ }
53
+ }, [basePath]);
54
+
55
+ // SignIn function
56
+ const signIn = useCallback(async (
57
+ provider: string = 'credentials',
58
+ options: SignInOptions = {}
59
+ ): Promise<SignInResult | undefined> => {
60
+ try {
61
+ const { redirect = true, callbackUrl, ...credentials } = options;
62
+
63
+ const response = await fetch(`${basePath}/signin`, {
64
+ method: 'POST',
65
+ headers: {
66
+ 'Content-Type': 'application/json',
67
+ },
68
+ credentials: 'include',
69
+ body: JSON.stringify({
70
+ provider,
71
+ ...credentials
72
+ })
73
+ });
74
+
75
+ const data = await response.json();
76
+
77
+ if (response.ok && data.success) {
78
+ // Atualiza a sessão após login bem-sucedido
79
+
80
+ if (redirect && typeof window !== 'undefined') {
81
+ try {
82
+ router.push(callbackUrl || '/');
83
+ } catch (e) {
84
+ window.location.href = callbackUrl || '/';
85
+ }
86
+
87
+ }
88
+ await fetchSession();
89
+
90
+ return {
91
+ ok: true,
92
+ status: 200,
93
+ url: callbackUrl || '/'
94
+ };
95
+ } else {
96
+ return {
97
+ error: data.error || 'Authentication failed',
98
+ status: response.status,
99
+ ok: false
100
+ };
101
+ }
102
+ } catch (error) {
103
+ console.error('[hweb-auth] Erro no signIn:', error);
104
+ return {
105
+ error: 'Network error',
106
+ status: 500,
107
+ ok: false
108
+ };
109
+ }
110
+ }, [basePath, fetchSession]);
111
+
112
+ // SignOut function
113
+ const signOut = useCallback(async (options: { callbackUrl?: string } = {}): Promise<void> => {
114
+ try {
115
+ await fetch(`${basePath}/signout`, {
116
+ method: 'POST',
117
+ credentials: 'include'
118
+ });
119
+
120
+ setSession(null);
121
+ setStatus('unauthenticated');
122
+
123
+ if (typeof window !== 'undefined') {
124
+ try {
125
+ router.push(options.callbackUrl || '/');
126
+ } catch (e) {
127
+ window.location.href = options.callbackUrl || '/';
128
+ }
129
+ }
130
+ } catch (error) {
131
+ console.error('[hweb-auth] Erro no signOut:', error);
132
+ }
133
+ }, [basePath]);
134
+
135
+ // Update session
136
+ const update = useCallback(async (): Promise<Session | null> => {
137
+ return await fetchSession();
138
+ }, [fetchSession]);
139
+
140
+ // Initial session fetch
141
+ useEffect(() => {
142
+ fetchSession();
143
+ }, [fetchSession]);
144
+
145
+ // Refetch interval
146
+ useEffect(() => {
147
+ if (refetchInterval > 0) {
148
+ const interval = setInterval(() => {
149
+ if (status === 'authenticated') {
150
+ fetchSession();
151
+ }
152
+ }, refetchInterval * 1000);
153
+
154
+ return () => clearInterval(interval);
155
+ }
156
+ }, [refetchInterval, status, fetchSession]);
157
+
158
+ // Refetch on window focus
159
+ useEffect(() => {
160
+ if (refetchOnWindowFocus) {
161
+ const handleFocus = () => {
162
+ if (status === 'authenticated') {
163
+ fetchSession();
164
+ }
165
+ };
166
+
167
+ window.addEventListener('focus', handleFocus);
168
+ return () => window.removeEventListener('focus', handleFocus);
169
+ }
170
+ }, [refetchOnWindowFocus, status, fetchSession]);
171
+
172
+ const value: SessionContextType = {
173
+ data: session,
174
+ status,
175
+ signIn,
176
+ signOut,
177
+ update
178
+ };
179
+
180
+ return (
181
+ <SessionContext.Provider value={value}>
182
+ {children}
183
+ </SessionContext.Provider>
184
+ );
185
+ }
186
+
187
+ /**
188
+ * Hook para acessar a sessão atual
189
+ */
190
+ export function useSession(): SessionContextType {
191
+ const context = useContext(SessionContext);
192
+ if (context === undefined) {
193
+ throw new Error('useSession deve ser usado dentro de um SessionProvider');
194
+ }
195
+ return context;
196
+ }
197
+
198
+ /**
199
+ * Hook para verificar se o usuário está autenticado
200
+ */
201
+ export function useAuth(): { user: User | null; isAuthenticated: boolean; isLoading: boolean } {
202
+ const { data: session, status } = useSession();
203
+
204
+ return {
205
+ user: session?.user || null,
206
+ isAuthenticated: status === 'authenticated',
207
+ isLoading: status === 'loading'
208
+ };
209
+ }