academe-kit 0.6.0 → 0.6.2

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
@@ -5772,6 +5772,54 @@ function createCourseService(apiClient) {
5772
5772
  };
5773
5773
  }
5774
5774
 
5775
+ function createStorageFileService(apiClient) {
5776
+ return {
5777
+ /**
5778
+ * Upload a new file
5779
+ * Note: This method requires FormData with a 'file' field
5780
+ */
5781
+ upload(file) {
5782
+ const formData = new FormData();
5783
+ formData.append("file", file);
5784
+ // Note: Route not typed in OpenAPI yet, using type assertion
5785
+ return apiClient.POST("/storage-files", {
5786
+ body: formData,
5787
+ bodySerializer: (body) => body,
5788
+ });
5789
+ },
5790
+ /**
5791
+ * List all storage files
5792
+ */
5793
+ getAll() {
5794
+ // Note: Route not typed in OpenAPI yet, using type assertion
5795
+ return apiClient.GET("/storage-files", {});
5796
+ },
5797
+ /**
5798
+ * Get storage file by ID
5799
+ */
5800
+ getById(id) {
5801
+ // Note: Route not typed in OpenAPI yet, using type assertion
5802
+ return apiClient.GET(`/storage-files/${id}`, {});
5803
+ },
5804
+ /**
5805
+ * Update storage file metadata
5806
+ */
5807
+ update(id, data) {
5808
+ // Note: Route not typed in OpenAPI yet, using type assertion
5809
+ return apiClient.PATCH(`/storage-files/${id}`, {
5810
+ body: data,
5811
+ });
5812
+ },
5813
+ /**
5814
+ * Delete storage file
5815
+ */
5816
+ delete(id) {
5817
+ // Note: Route not typed in OpenAPI yet, using type assertion
5818
+ return apiClient.DELETE(`/storage-files/${id}`, {});
5819
+ },
5820
+ };
5821
+ }
5822
+
5775
5823
  function createAcademeApiClient(baseUrl) {
5776
5824
  return createClient({ baseUrl });
5777
5825
  }
@@ -5793,6 +5841,7 @@ function createAcademeServices(apiClient) {
5793
5841
  certificateTemplate: createCertificateTemplateService(apiClient),
5794
5842
  seatCode: createSeatCodeService(apiClient),
5795
5843
  product: createProductService(apiClient),
5844
+ storageFile: createStorageFileService(apiClient),
5796
5845
  };
5797
5846
  }
5798
5847
 
@@ -5805,6 +5854,37 @@ exports.GLOBAL_ROLES = void 0;
5805
5854
  GLOBAL_ROLES["GUARDIAN"] = "guardian";
5806
5855
  })(exports.GLOBAL_ROLES || (exports.GLOBAL_ROLES = {}));
5807
5856
 
5857
+ function detectMobileTokenSync() {
5858
+ if (typeof window === "undefined") {
5859
+ return { token: undefined, isMobile: false, decoded: null };
5860
+ }
5861
+ const keycloakConfig = window.keycloakConfig;
5862
+ if (!keycloakConfig?.fromMobile || !keycloakConfig?.token) {
5863
+ return { token: undefined, isMobile: false, decoded: null };
5864
+ }
5865
+ // Validar e decodificar o token mobile
5866
+ try {
5867
+ const decoded = jwtDecode(keycloakConfig.token);
5868
+ if (!decoded) {
5869
+ console.error("[SecurityProvider] Token mobile inválido - ignorando");
5870
+ return { token: undefined, isMobile: false, decoded: null };
5871
+ }
5872
+ // Verificar se o token não está expirado
5873
+ const now = Math.floor(Date.now() / 1000);
5874
+ if (decoded.exp && decoded.exp < now) {
5875
+ console.error("[SecurityProvider] Token mobile expirado - ignorando");
5876
+ return { token: undefined, isMobile: false, decoded: null };
5877
+ }
5878
+ console.log("[SecurityProvider] ✅ Token mobile detectado síncronamente");
5879
+ return { token: keycloakConfig.token, isMobile: true, decoded };
5880
+ }
5881
+ catch (error) {
5882
+ console.error("[SecurityProvider] Erro ao decodificar token mobile:", error);
5883
+ return { token: undefined, isMobile: false, decoded: null };
5884
+ }
5885
+ }
5886
+ // Detecta o token mobile UMA VEZ no carregamento do módulo
5887
+ const initialMobileState = detectMobileTokenSync();
5808
5888
  const AcademeAuthProvider = ({ realm, hubUrl, children, clientId, keycloakUrl, apiBaseUrl, skipApiUserFetch, }) => {
5809
5889
  const oidcConfig = {
5810
5890
  authority: `${keycloakUrl}/realms/${realm}`,
@@ -5851,12 +5931,14 @@ const decodeAccessToken = (token) => {
5851
5931
  const SecurityProvider = ({ apiBaseUrl = "https://stg-api.academe.com.br", skipApiUserFetch = false, children, }) => {
5852
5932
  const auth = useAuth();
5853
5933
  const [currentUser, setCurrentUser] = React2.useState(null);
5854
- const hasTriedSignInSilent = React2.useRef(false);
5855
- const [isTokenReady, setIsTokenReady] = React2.useState(false);
5856
- // --- Estado para autenticação mobile ---
5857
- const [isMobileAuth, setIsMobileAuth] = React2.useState(false);
5858
- const [mobileToken, setMobileToken] = React2.useState(undefined);
5859
- const mobileInitializedRef = React2.useRef(false);
5934
+ const [isRefreshing, setIsRefreshing] = React2.useState(false);
5935
+ // CRÍTICO: Inicializar estados com valores do token mobile detectado síncronamente
5936
+ // Isso garante que no primeiro render já temos os valores corretos
5937
+ const hasTriedSignInSilent = React2.useRef(initialMobileState.isMobile);
5938
+ const [isTokenReady, setIsTokenReady] = React2.useState(initialMobileState.isMobile);
5939
+ // --- Estado para autenticação mobile (inicializado com detecção síncrona) ---
5940
+ const [isMobileAuth, setIsMobileAuth] = React2.useState(initialMobileState.isMobile);
5941
+ const [mobileToken, setMobileToken] = React2.useState(initialMobileState.token);
5860
5942
  // Ref para armazenar o resolver da Promise de token
5861
5943
  const tokenReadyResolverRef = React2.useRef(null);
5862
5944
  const tokenReadyPromiseRef = React2.useRef(null);
@@ -5867,48 +5949,13 @@ const SecurityProvider = ({ apiBaseUrl = "https://stg-api.academe.com.br", skipA
5867
5949
  const accessToken = auth.user?.access_token;
5868
5950
  const userProfile = auth.user?.profile;
5869
5951
  const userProfileSub = auth.user?.profile?.sub;
5870
- // Ref para armazenar o token atual (acessível no middleware)
5871
- const currentTokenRef = React2.useRef(undefined);
5872
- // --- 0. Detecção de Token Mobile (ANTES do SSO check) ---
5873
- React2.useEffect(() => {
5874
- // executa uma vez na inicialização
5875
- if (mobileInitializedRef.current)
5876
- return;
5877
- mobileInitializedRef.current = true;
5878
- if (typeof window === "undefined")
5879
- return;
5880
- const keycloakConfig = window.keycloakConfig;
5881
- if (!keycloakConfig?.fromMobile || !keycloakConfig?.token) {
5882
- console.debug("[SecurityProvider] Modo web normal - sem token mobile");
5883
- return;
5884
- }
5885
- // Validar e decodificar o token mobile
5886
- const decoded = decodeAccessToken(keycloakConfig.token);
5887
- if (!decoded) {
5888
- console.error("[SecurityProvider] Token mobile inválido - ignorando");
5889
- return;
5890
- }
5891
- // Verificar se o token não está expirado
5892
- const now = Math.floor(Date.now() / 1000);
5893
- if (decoded.exp && decoded.exp < now) {
5894
- console.error("[SecurityProvider] Token mobile expirado - ignorando");
5895
- return;
5896
- }
5897
- console.log("[SecurityProvider] ✅ Token mobile detectado e válido");
5898
- // Ativar modo mobile
5899
- setMobileToken(keycloakConfig.token);
5900
- setIsMobileAuth(true);
5901
- // Sincronizar com window.accessToken e currentTokenRef
5902
- window.accessToken = keycloakConfig.token;
5903
- currentTokenRef.current = keycloakConfig.token;
5904
- // Marcar que já tentamos SSO (para pular o check)
5905
- hasTriedSignInSilent.current = true;
5906
- // Resolver promise de token imediatamente
5907
- if (tokenReadyResolverRef.current) {
5908
- tokenReadyResolverRef.current();
5909
- }
5910
- setIsTokenReady(true);
5911
- }, []);
5952
+ // CRÍTICO: Inicializar currentTokenRef com o token mobile detectado síncronamente
5953
+ // Isso garante que o middleware do apiClient já tenha o token no primeiro request
5954
+ const currentTokenRef = React2.useRef(initialMobileState.token);
5955
+ // Sincronizar window.accessToken se tivermos token mobile (executa síncronamente)
5956
+ if (initialMobileState.token && typeof window !== "undefined" && !window.accessToken) {
5957
+ window.accessToken = initialMobileState.token;
5958
+ }
5912
5959
  // --- 1. Silent Check Inicial (Check SSO) - Pulado se mobile ---
5913
5960
  React2.useEffect(() => {
5914
5961
  // Pular SSO check se já estamos em modo mobile
@@ -5930,10 +5977,18 @@ const SecurityProvider = ({ apiBaseUrl = "https://stg-api.academe.com.br", skipA
5930
5977
  // --- 2. Configuração de API e Services ---
5931
5978
  const apiClient = React2.useMemo(() => {
5932
5979
  const client = createAcademeApiClient(apiBaseUrl);
5933
- // Inicializa a Promise de token ready
5934
- tokenReadyPromiseRef.current = new Promise((resolve) => {
5935
- tokenReadyResolverRef.current = resolve;
5936
- });
5980
+ // CRÍTICO: Se temos token mobile, criar Promise já resolvida
5981
+ // Isso evita que o middleware aguarde uma Promise que nunca será resolvida
5982
+ if (initialMobileState.token) {
5983
+ tokenReadyPromiseRef.current = Promise.resolve();
5984
+ tokenReadyResolverRef.current = () => { };
5985
+ }
5986
+ else {
5987
+ // Inicializa a Promise de token ready para modo OIDC
5988
+ tokenReadyPromiseRef.current = new Promise((resolve) => {
5989
+ tokenReadyResolverRef.current = resolve;
5990
+ });
5991
+ }
5937
5992
  // Middleware que aguarda o token estar disponível antes de fazer requests
5938
5993
  client.use({
5939
5994
  async onRequest({ request }) {
@@ -6004,6 +6059,7 @@ const SecurityProvider = ({ apiBaseUrl = "https://stg-api.academe.com.br", skipA
6004
6059
  email: profile?.email || "",
6005
6060
  name: profile?.given_name || "",
6006
6061
  lastName: profile?.family_name || "",
6062
+ avatar_url: decodedAccessToken?.avatar_url
6007
6063
  };
6008
6064
  }, [userProfile, isMobileAuth, decodedAccessToken]);
6009
6065
  const hasRealmRole = React2.useCallback((role) => {
@@ -6071,8 +6127,10 @@ const SecurityProvider = ({ apiBaseUrl = "https://stg-api.academe.com.br", skipA
6071
6127
  isMobileAuth,
6072
6128
  ]);
6073
6129
  const refreshUserData = React2.useCallback(async () => {
6130
+ setIsRefreshing(true);
6074
6131
  if (effectiveIsAuthenticated) {
6075
6132
  if (skipApiUserFetch) {
6133
+ await auth.signinSilent();
6076
6134
  const academeUser = {
6077
6135
  keycloakUser: getKeycloakUser(),
6078
6136
  };
@@ -6093,6 +6151,7 @@ const SecurityProvider = ({ apiBaseUrl = "https://stg-api.academe.com.br", skipA
6093
6151
  console.error("[SecurityProvider] Error refreshing user data:", error);
6094
6152
  }
6095
6153
  }
6154
+ setIsRefreshing(false);
6096
6155
  }, [effectiveIsAuthenticated, getKeycloakUser, services, skipApiUserFetch]);
6097
6156
  // --- 5. Ações de Auth ---
6098
6157
  // SignOut para modo OIDC (web normal)
@@ -6148,7 +6207,7 @@ const SecurityProvider = ({ apiBaseUrl = "https://stg-api.academe.com.br", skipA
6148
6207
  }, [isMobileAuth]);
6149
6208
  // Memoizar o value do context para evitar re-renders desnecessários
6150
6209
  const contextValue = React2.useMemo(() => ({
6151
- isInitialized: isMobileAuth ? true : !isLoading,
6210
+ isInitialized: isMobileAuth ? true : (!isLoading || isRefreshing),
6152
6211
  isTokenReady,
6153
6212
  isMobileAuth,
6154
6213
  user: currentUser,