File size: 4,185 Bytes
f52d137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import { Tooltip } from "@mui/material";
import React, { useCallback, useEffect } from "react";
import { UserInfo } from "../types";
import {
  exchangeCodeForToken,
  fetchUserInfo,
  readFragmentParams,
  startLogin,
} from "../utils/oauth";
import { LoginButton } from "./LoginButton";

interface HuggingFaceLoginButtonProps {
  userInfo: UserInfo | null;
  accessToken: string | null;
  loginLabel: string;
  isDisabled?: boolean;
  onLoginStateChange: (
    userInfo: UserInfo | null,
    accessToken: string | null,
    loginLabel: string,
  ) => void;
}

export const HuggingFaceLoginButton: React.FC<HuggingFaceLoginButtonProps> = ({
  userInfo,
  accessToken,
  loginLabel,
  isDisabled = false,
  onLoginStateChange,
}) => {
  const isLoggedIn = !!userInfo?.sub;
  // Handle OAuth redirect
  const handleRedirect = useCallback(async () => {
    const params = new URLSearchParams(window.location.search);
    const { access_token: fragToken, error: fragErr } = readFragmentParams();

    if (fragErr) {
      onLoginStateChange(null, null, `Error: ${fragErr}`);
      return true;
    }

    const error = params.get("error");
    const errorDescription = params.get("error_description");
    if (error) {
      onLoginStateChange(
        null,
        null,
        `Error: ${error}${errorDescription ? ` — ${errorDescription}` : ""}`,
      );
      return true;
    }

    const returnedState = params.get("state");
    const expectedState = sessionStorage.getItem("hf_oauth_state");
    if (returnedState && expectedState && returnedState !== expectedState) {
      onLoginStateChange(null, null, "Error: invalid state");
      return true;
    }

    // Implicit flow
    if (fragToken) {
      try {
        const info = await fetchUserInfo(fragToken);
        const label =
          info?.email || info?.name || info?.preferred_username || "User";
        onLoginStateChange(info, fragToken, label);
      } catch (err) {
        console.error(err);
        onLoginStateChange(null, fragToken, "Connected");
      }
      window.history.replaceState({}, "", window.location.pathname);
      return true;
    }

    const code = params.get("code");
    if (!code) return false;

    try {
      const tokenResponse = await exchangeCodeForToken(code);
      const token = tokenResponse.access_token;
      if (token) {
        try {
          const info = await fetchUserInfo(token);
          const label =
            info?.email || info?.name || info?.preferred_username || "User";
          onLoginStateChange(info, token, label);
        } catch (err) {
          console.error(err);
          onLoginStateChange(null, token, "Connected");
        }
      } else {
        onLoginStateChange(null, null, "Connected");
      }
    } catch (e: any) {
      console.error(e);
      onLoginStateChange(null, null, `Authentication error: ${e.message}`);
    } finally {
      window.history.replaceState({}, "", window.location.pathname);
    }

    return true;
  }, [onLoginStateChange]);

  // Initialize on component mount
  useEffect(() => {
    const initialize = async () => {
      const handled = await handleRedirect();
      if (!handled) {
        onLoginStateChange(null, null, "Login with Hugging Face");
      }
    };
    initialize();
  }, [handleRedirect, onLoginStateChange]);

  const handleLoginClick = () => {
    onLoginStateChange(userInfo, accessToken, "Redirecting to Hugging Face…");
    startLogin().catch((err) => {
      console.error(err);
      onLoginStateChange(userInfo, accessToken, `Error: ${err.message}`);
    });
  };

  return (
    <Tooltip title={isLoggedIn ? "Log out" : ""} placement="right">
      <LoginButton
        icon={
          <img
            src="https://huggingface.co/front/assets/huggingface_logo-noborder.svg"
            alt="Hugging Face"
            style={{ width: 18, height: 18 }}
          />
        }
        onClick={
          isLoggedIn
            ? () => onLoginStateChange(null, null, "Login with Hugging Face")
            : handleLoginClick
        }
        isLoggedIn={isLoggedIn}
        disabled={isDisabled}
      >
        {loginLabel}
      </LoginButton>
    </Tooltip>
  );
};