academe-kit 0.6.7 → 0.7.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.
package/dist/index.cjs CHANGED
@@ -5984,37 +5984,20 @@ exports.GLOBAL_ROLES = void 0;
5984
5984
  GLOBAL_ROLES["GUARDIAN"] = "guardian";
5985
5985
  })(exports.GLOBAL_ROLES || (exports.GLOBAL_ROLES = {}));
5986
5986
 
5987
- function detectMobileTokenSync() {
5988
- if (typeof window === "undefined") {
5989
- return { token: undefined, isMobile: false, decoded: null };
5990
- }
5991
- const keycloakConfig = window.keycloakConfig;
5992
- if (!keycloakConfig?.fromMobile || !keycloakConfig?.token) {
5993
- return { token: undefined, isMobile: false, decoded: null };
5994
- }
5995
- // Validar e decodificar o token mobile
5996
- try {
5997
- const decoded = jwtDecode(keycloakConfig.token);
5998
- if (!decoded) {
5999
- console.error("[SecurityProvider] Token mobile inválido - ignorando");
6000
- return { token: undefined, isMobile: false, decoded: null };
6001
- }
6002
- // Verificar se o token não está expirado
6003
- const now = Math.floor(Date.now() / 1000);
6004
- if (decoded.exp && decoded.exp < now) {
6005
- console.error("[SecurityProvider] Token mobile expirado - ignorando");
6006
- return { token: undefined, isMobile: false, decoded: null };
6007
- }
6008
- console.log("[SecurityProvider] ✅ Token mobile detectado síncronamente");
6009
- return { token: keycloakConfig.token, isMobile: true, decoded };
6010
- }
6011
- catch (error) {
6012
- console.error("[SecurityProvider] Erro ao decodificar token mobile:", error);
6013
- return { token: undefined, isMobile: false, decoded: null };
5987
+ const getCookie = (name) => {
5988
+ if (typeof window === "undefined")
5989
+ return null;
5990
+ const value = `; ${document.cookie}`;
5991
+ const parts = value.split(`; ${name}=`);
5992
+ if (parts.length === 2) {
5993
+ return parts.pop()?.split(";").shift() || null;
6014
5994
  }
6015
- }
6016
- // Detecta o token mobile UMA VEZ no carregamento do módulo
6017
- const initialMobileState = detectMobileTokenSync();
5995
+ return null;
5996
+ };
5997
+ const getAccessTokenFromCookies = () => {
5998
+ const token = getCookie("KC_FORCE_AUTH_TOKEN");
5999
+ return token || undefined;
6000
+ };
6018
6001
  const AcademeAuthProvider = ({ realm, hubUrl, children, clientId, keycloakUrl, apiBaseUrl, skipApiUserFetch, }) => {
6019
6002
  const oidcConfig = {
6020
6003
  authority: `${keycloakUrl}/realms/${realm}`,
@@ -6033,7 +6016,6 @@ const AcademeAuthProvider = ({ realm, hubUrl, children, clientId, keycloakUrl, a
6033
6016
  const SecurityContext = React2.createContext({
6034
6017
  isInitialized: false,
6035
6018
  isTokenReady: false,
6036
- isMobileAuth: false,
6037
6019
  user: null,
6038
6020
  refreshUserData: async () => { },
6039
6021
  signOut: () => null,
@@ -6062,35 +6044,53 @@ const SecurityProvider = ({ apiBaseUrl = "https://stg-api.academe.com.br", skipA
6062
6044
  const auth = useAuth();
6063
6045
  const [currentUser, setCurrentUser] = React2.useState(null);
6064
6046
  const [isRefreshing, setIsRefreshing] = React2.useState(false);
6065
- // CRÍTICO: Inicializar estados com valores do token mobile detectado síncronamente
6066
- // Isso garante que no primeiro render já temos os valores corretos
6067
- const hasTriedSignInSilent = React2.useRef(initialMobileState.isMobile);
6068
- const [isTokenReady, setIsTokenReady] = React2.useState(initialMobileState.isMobile);
6069
- // --- Estado para autenticação mobile (inicializado com detecção síncrona) ---
6070
- const [isMobileAuth, setIsMobileAuth] = React2.useState(initialMobileState.isMobile);
6071
- const [mobileToken, setMobileToken] = React2.useState(initialMobileState.token);
6072
- // Ref para armazenar o resolver da Promise de token
6047
+ const hasTriedSignInSilent = React2.useRef(false);
6048
+ const [isTokenReady, setIsTokenReady] = React2.useState(false);
6073
6049
  const tokenReadyResolverRef = React2.useRef(null);
6074
6050
  const tokenReadyPromiseRef = React2.useRef(null);
6075
- // Extrair valores primitivos do auth para usar como dependências estáveis
6076
- const isAuthenticated = auth.isAuthenticated;
6051
+ const [accessToken, setAccessToken] = React2.useState(() => {
6052
+ const cookieToken = getAccessTokenFromCookies();
6053
+ return cookieToken || auth.user?.access_token;
6054
+ });
6055
+ const decodedAccessToken = React2.useMemo(() => {
6056
+ return accessToken ? decodeAccessToken(accessToken) : null;
6057
+ }, [accessToken]);
6058
+ const userProfileSub = auth.user?.profile?.sub || decodedAccessToken?.sub;
6059
+ const isAuthenticated = auth.isAuthenticated || !!accessToken;
6077
6060
  const isLoading = auth.isLoading;
6078
6061
  const activeNavigator = auth.activeNavigator;
6079
- const accessToken = auth.user?.access_token;
6080
6062
  const userProfile = auth.user?.profile;
6081
- const userProfileSub = auth.user?.profile?.sub;
6082
- // CRÍTICO: Inicializar currentTokenRef com o token mobile detectado síncronamente
6083
- // Isso garante que o middleware do apiClient já tenha o token no primeiro request
6084
- const currentTokenRef = React2.useRef(initialMobileState.token);
6085
- // Sincronizar window.accessToken se tivermos token mobile (executa síncronamente)
6086
- if (initialMobileState.token && typeof window !== "undefined" && !window.accessToken) {
6087
- window.accessToken = initialMobileState.token;
6088
- }
6089
- // --- 1. Silent Check Inicial (Check SSO) - Pulado se mobile ---
6063
+ console.log(">> isAuthenticated:", isAuthenticated);
6064
+ console.log(">> accessToken:", accessToken);
6065
+ const currentTokenRef = React2.useRef(undefined);
6066
+ // Efeito para sincronizar o token do auth com o state
6067
+ // e também verificar cookies periodicamente
6068
+ React2.useEffect(() => {
6069
+ const cookieToken = getAccessTokenFromCookies();
6070
+ const authToken = auth.user?.access_token;
6071
+ // Prioridade: Cookie > Auth token
6072
+ const newToken = cookieToken || authToken;
6073
+ if (newToken && newToken !== accessToken) {
6074
+ setAccessToken(newToken);
6075
+ }
6076
+ }, [auth.user?.access_token, accessToken]);
6077
+ // Efeito opcional: verificar cookies em intervalos (útil se o cookie pode mudar externamente)
6090
6078
  React2.useEffect(() => {
6091
- // Pular SSO check se já estamos em modo mobile
6092
- if (isMobileAuth) {
6093
- console.debug("[SecurityProvider] Modo mobile - pulando SSO check");
6079
+ const checkCookieToken = () => {
6080
+ const cookieToken = getAccessTokenFromCookies();
6081
+ if (cookieToken && cookieToken !== accessToken) {
6082
+ setAccessToken(cookieToken);
6083
+ }
6084
+ };
6085
+ // Verifica a cada 5 segundos (ajuste conforme necessário)
6086
+ const interval = setInterval(checkCookieToken, 5000);
6087
+ return () => clearInterval(interval);
6088
+ }, [accessToken]);
6089
+ // --- 1. Silent Check Inicial (Check SSO) ---
6090
+ React2.useEffect(() => {
6091
+ // Se já temos um token do cookie, não precisamos fazer silent check
6092
+ if (accessToken) {
6093
+ hasTriedSignInSilent.current = true;
6094
6094
  return;
6095
6095
  }
6096
6096
  if (!isAuthenticated &&
@@ -6103,31 +6103,19 @@ const SecurityProvider = ({ apiBaseUrl = "https://stg-api.academe.com.br", skipA
6103
6103
  });
6104
6104
  }
6105
6105
  // eslint-disable-next-line react-hooks/exhaustive-deps
6106
- }, [isAuthenticated, isLoading, activeNavigator, isMobileAuth]);
6106
+ }, [isAuthenticated, isLoading, activeNavigator, accessToken]);
6107
6107
  // --- 2. Configuração de API e Services ---
6108
6108
  const apiClient = React2.useMemo(() => {
6109
6109
  const client = createAcademeApiClient(apiBaseUrl);
6110
- // CRÍTICO: Se já temos token mobile, criar Promise resolvida
6111
- // Isso evita que o middleware aguarde uma Promise que nunca será resolvida
6112
- if (initialMobileState.token) {
6113
- tokenReadyPromiseRef.current = Promise.resolve();
6114
- tokenReadyResolverRef.current = () => { };
6115
- }
6116
- else {
6117
- // Inicializa a Promise de token ready para modo OIDC
6118
- tokenReadyPromiseRef.current = new Promise((resolve) => {
6119
- tokenReadyResolverRef.current = resolve;
6120
- });
6121
- }
6122
- // Middleware que aguarda o token estar disponível antes de fazer requests
6110
+ tokenReadyPromiseRef.current = new Promise((resolve) => {
6111
+ tokenReadyResolverRef.current = resolve;
6112
+ });
6123
6113
  client.use({
6124
6114
  async onRequest({ request }) {
6125
- // Se já tem token, usa imediatamente
6126
6115
  if (currentTokenRef.current) {
6127
6116
  request.headers.set("Authorization", `Bearer ${currentTokenRef.current}`);
6128
6117
  return request;
6129
6118
  }
6130
- // Se ainda está carregando a auth, aguarda o token ficar pronto
6131
6119
  if (tokenReadyPromiseRef.current) {
6132
6120
  await tokenReadyPromiseRef.current;
6133
6121
  if (currentTokenRef.current) {
@@ -6142,56 +6130,33 @@ const SecurityProvider = ({ apiBaseUrl = "https://stg-api.academe.com.br", skipA
6142
6130
  const services = React2.useMemo(() => {
6143
6131
  return createAcademeServices(apiClient);
6144
6132
  }, [apiClient]);
6145
- // Token efetivo: prioriza mobile, depois OIDC
6146
- const effectiveToken = mobileToken || accessToken;
6147
- const decodedAccessToken = React2.useMemo(() => {
6148
- return effectiveToken ? decodeAccessToken(effectiveToken) : null;
6149
- }, [effectiveToken]);
6150
- // Atualização do Token e resolução da Promise
6151
- // Não sobrescreve se estamos em modo mobile
6152
6133
  React2.useEffect(() => {
6153
- // Se estamos em modo mobile, o token já foi configurado
6154
- if (isMobileAuth) {
6155
- return;
6156
- }
6157
6134
  currentTokenRef.current = accessToken;
6158
6135
  if (typeof window !== "undefined") {
6159
6136
  window.accessToken = accessToken;
6160
6137
  }
6161
6138
  if (accessToken) {
6162
- // Resolve a promise indicando que o token está pronto
6163
6139
  if (tokenReadyResolverRef.current) {
6164
6140
  tokenReadyResolverRef.current();
6165
6141
  setIsTokenReady(true);
6166
6142
  }
6167
6143
  }
6168
6144
  else if (!isLoading && !isAuthenticated) {
6169
- // Usuário não autenticado - resolve a promise para não bloquear requests públicas
6170
6145
  if (tokenReadyResolverRef.current) {
6171
6146
  tokenReadyResolverRef.current();
6172
6147
  setIsTokenReady(true);
6173
6148
  }
6174
6149
  }
6175
- }, [accessToken, isLoading, isAuthenticated, isMobileAuth]);
6176
- // --- 3. Helpers de Usuário e Roles ---
6150
+ }, [accessToken, isLoading, isAuthenticated]);
6177
6151
  const getKeycloakUser = React2.useCallback(() => {
6178
- // Se estamos em modo mobile, extrair do token decodificado
6179
- if (isMobileAuth && decodedAccessToken) {
6180
- return {
6181
- email: decodedAccessToken.email || "",
6182
- name: decodedAccessToken.given_name || "",
6183
- lastName: decodedAccessToken.family_name || "",
6184
- };
6185
- }
6186
- // Modo OIDC normal
6187
6152
  const profile = userProfile;
6188
6153
  return {
6189
- email: profile?.email || "",
6190
- name: profile?.given_name || "",
6191
- lastName: profile?.family_name || "",
6192
- avatar_url: decodedAccessToken?.avatar_url
6154
+ email: profile?.email || decodedAccessToken?.email || "",
6155
+ name: profile?.given_name || decodedAccessToken?.given_name || "",
6156
+ lastName: profile?.family_name || decodedAccessToken?.family_name || "",
6157
+ avatar_url: decodedAccessToken?.avatar_url,
6193
6158
  };
6194
- }, [userProfile, isMobileAuth, decodedAccessToken]);
6159
+ }, [userProfile, decodedAccessToken]);
6195
6160
  const hasRealmRole = React2.useCallback((role) => {
6196
6161
  return decodedAccessToken?.realm_access?.roles?.includes(role) ?? false;
6197
6162
  }, [decodedAccessToken]);
@@ -6206,20 +6171,17 @@ const SecurityProvider = ({ apiBaseUrl = "https://stg-api.academe.com.br", skipA
6206
6171
  }
6207
6172
  return Object.values(decodedAccessToken.resource_access).some((resource) => resource.roles?.includes(role));
6208
6173
  }, [decodedAccessToken]);
6209
- // Autenticação efetiva: mobile ou OIDC
6210
- const effectiveIsAuthenticated = isMobileAuth || isAuthenticated;
6211
- const effectiveUserSub = isMobileAuth ? decodedAccessToken?.sub : userProfileSub;
6212
6174
  // --- 4. Fetch de Dados do Usuário (Backend) ---
6213
6175
  React2.useEffect(() => {
6214
6176
  let isMounted = true;
6215
6177
  const fetchUserData = async () => {
6216
- if (effectiveIsAuthenticated) {
6178
+ if (isAuthenticated) {
6217
6179
  if (skipApiUserFetch) {
6218
6180
  if (isMounted) {
6219
6181
  const academeUser = {
6220
6182
  keycloakUser: getKeycloakUser(),
6221
6183
  };
6222
- setCurrentUser({ ...academeUser, id: effectiveUserSub || "" });
6184
+ setCurrentUser({ ...academeUser, id: userProfileSub || "" });
6223
6185
  }
6224
6186
  return;
6225
6187
  }
@@ -6237,7 +6199,7 @@ const SecurityProvider = ({ apiBaseUrl = "https://stg-api.academe.com.br", skipA
6237
6199
  console.error("[SecurityProvider] Error fetching user data:", error);
6238
6200
  }
6239
6201
  }
6240
- else if (!effectiveIsAuthenticated && !isLoading && !isMobileAuth) {
6202
+ else if (!isAuthenticated && !isLoading) {
6241
6203
  if (isMounted) {
6242
6204
  setCurrentUser(null);
6243
6205
  }
@@ -6248,17 +6210,16 @@ const SecurityProvider = ({ apiBaseUrl = "https://stg-api.academe.com.br", skipA
6248
6210
  isMounted = false;
6249
6211
  };
6250
6212
  }, [
6251
- effectiveIsAuthenticated,
6213
+ isAuthenticated,
6252
6214
  isLoading,
6253
6215
  getKeycloakUser,
6254
6216
  services,
6255
6217
  skipApiUserFetch,
6256
- effectiveUserSub,
6257
- isMobileAuth,
6218
+ userProfileSub,
6258
6219
  ]);
6259
6220
  const refreshUserData = React2.useCallback(async () => {
6260
6221
  setIsRefreshing(true);
6261
- if (effectiveIsAuthenticated) {
6222
+ if (isAuthenticated) {
6262
6223
  if (skipApiUserFetch) {
6263
6224
  await auth.signinSilent();
6264
6225
  const academeUser = {
@@ -6282,47 +6243,17 @@ const SecurityProvider = ({ apiBaseUrl = "https://stg-api.academe.com.br", skipA
6282
6243
  }
6283
6244
  }
6284
6245
  setIsRefreshing(false);
6285
- }, [effectiveIsAuthenticated, getKeycloakUser, services, skipApiUserFetch]);
6246
+ }, [getKeycloakUser, services, skipApiUserFetch]);
6286
6247
  // --- 5. Ações de Auth ---
6287
- // SignOut para modo OIDC (web normal)
6288
- const signOutOidc = React2.useCallback(() => {
6289
- console.log("[KC LOGOUT - OIDC]");
6248
+ const signOut = React2.useCallback(() => {
6249
+ console.log("[KC LOGOUT!]");
6290
6250
  setCurrentUser(null);
6291
6251
  auth.removeUser();
6292
- auth.signoutSilent({
6252
+ auth.signoutRedirect({
6293
6253
  id_token_hint: auth.user?.id_token,
6294
6254
  });
6295
6255
  auth.clearStaleState();
6296
- setTimeout(() => {
6297
- window.location.reload();
6298
- }, 500);
6299
6256
  }, [auth]);
6300
- // SignOut para modo mobile
6301
- const signOutMobile = React2.useCallback(() => {
6302
- console.log("[KC LOGOUT - Mobile]");
6303
- setCurrentUser(null);
6304
- setMobileToken(undefined);
6305
- setIsMobileAuth(false);
6306
- // Limpar tokens globais
6307
- if (typeof window !== "undefined") {
6308
- window.accessToken = undefined;
6309
- window.keycloakConfig = undefined;
6310
- }
6311
- currentTokenRef.current = undefined;
6312
- // Notificar o app mobile para fazer logout
6313
- if (window.ReactNativeWebView) {
6314
- window.ReactNativeWebView.postMessage(JSON.stringify({ type: "LOGOUT_REQUESTED" }));
6315
- }
6316
- }, []);
6317
- // SignOut unificado: escolhe automaticamente baseado no modo
6318
- const signOut = React2.useCallback(() => {
6319
- if (isMobileAuth) {
6320
- signOutMobile();
6321
- }
6322
- else {
6323
- signOutOidc();
6324
- }
6325
- }, [isMobileAuth, signOutMobile, signOutOidc]);
6326
6257
  const hasSchool = React2.useCallback((schoolId) => {
6327
6258
  if (hasRealmRole(exports.GLOBAL_ROLES.ADMIN_ACADEME)) {
6328
6259
  return true;
@@ -6330,43 +6261,36 @@ const SecurityProvider = ({ apiBaseUrl = "https://stg-api.academe.com.br", skipA
6330
6261
  return (currentUser?.institutionRegistrations?.some((registration) => registration.institutionId === schoolId) ?? false);
6331
6262
  }, [hasRealmRole, currentUser?.institutionRegistrations]);
6332
6263
  const goToLogin = React2.useCallback(() => {
6333
- // Em modo mobile, notificar o app para fazer login
6334
- if (isMobileAuth && window.ReactNativeWebView) {
6335
- window.ReactNativeWebView.postMessage(JSON.stringify({ type: "LOGIN_REQUESTED" }));
6336
- return Promise.resolve();
6337
- }
6338
6264
  return auth.signinRedirect();
6339
6265
  // eslint-disable-next-line react-hooks/exhaustive-deps
6340
- }, [isMobileAuth]);
6266
+ }, []);
6341
6267
  // Memoizar o value do context para evitar re-renders desnecessários
6342
6268
  const contextValue = React2.useMemo(() => ({
6343
- isInitialized: isMobileAuth ? true : (!isLoading || isRefreshing),
6269
+ isInitialized: !isLoading || isRefreshing,
6344
6270
  isTokenReady,
6345
- isMobileAuth,
6346
6271
  user: currentUser,
6347
6272
  refreshUserData,
6348
6273
  signOut,
6349
- isAuthenticated: () => effectiveIsAuthenticated,
6274
+ isAuthenticated: () => isAuthenticated,
6350
6275
  hasSchool,
6351
6276
  goToLogin,
6352
6277
  hasRealmRole,
6353
6278
  hasClientRole,
6354
- accessToken: effectiveToken,
6279
+ accessToken,
6355
6280
  apiClient,
6356
6281
  services,
6357
6282
  }), [
6358
- isMobileAuth,
6359
6283
  isLoading,
6360
6284
  isTokenReady,
6361
6285
  currentUser,
6362
6286
  refreshUserData,
6363
6287
  signOut,
6364
- effectiveIsAuthenticated,
6288
+ isAuthenticated,
6365
6289
  hasSchool,
6366
6290
  goToLogin,
6367
6291
  hasRealmRole,
6368
6292
  hasClientRole,
6369
- effectiveToken,
6293
+ accessToken,
6370
6294
  apiClient,
6371
6295
  services,
6372
6296
  ]);