github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/wsctl/client/client.go (about) 1 package client 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "os" 10 "sync/atomic" 11 12 "github.com/pkg/errors" 13 "github.com/tidwall/gjson" 14 "golang.org/x/term" 15 16 "github.com/machinefi/w3bstream/pkg/depends/conf/log" 17 "github.com/machinefi/w3bstream/pkg/wsctl/config" 18 ) 19 20 // Multi-language support 21 var ( 22 _userNameInput = map[config.Language]string{ 23 config.English: "Please enter your name: ", 24 config.Chinese: "请输入用户名:", 25 } 26 _userPasswordInput = map[config.Language]string{ 27 config.English: "Please enter password: ", 28 config.Chinese: "请输入密码:", 29 } 30 ) 31 32 // Client defines the interface of an wsctl client 33 type Client interface { 34 // Config returns the config of the client 35 Config() config.Config 36 // ConfigFilePath returns the file path of the config 37 ConfigFilePath() string 38 // SelectTranslation select a translation based on UILanguage 39 SelectTranslation(map[config.Language]string) string 40 // Call http call 41 Call(url string, req *http.Request) ([]byte, error) 42 } 43 44 type client struct { 45 cfg config.Config 46 configFilePath string 47 logger log.Logger 48 token atomic.Value 49 } 50 51 // NewClient creates a new wsctl client 52 func NewClient(cfg config.Config, configFilePath string, logger log.Logger) Client { 53 return &client{ 54 cfg: cfg, 55 configFilePath: configFilePath, 56 logger: logger, 57 } 58 } 59 60 func (c *client) Config() config.Config { 61 return c.cfg 62 } 63 64 func (c *client) ConfigFilePath() string { 65 return c.configFilePath 66 } 67 68 func (c *client) SelectTranslation(trls map[config.Language]string) string { 69 trl, ok := trls[c.cfg.Language] 70 if !ok { 71 c.logger.Panic(errors.New("failed to pick a translation")) 72 } 73 return trl 74 } 75 76 func (c *client) Call(url string, req *http.Request) ([]byte, error) { 77 resp, err := c.call(url, req, c.getToken()) 78 if err != nil { 79 return nil, errors.Wrap(err, "failed to call w3bstream api") 80 } 81 body, err := ioutil.ReadAll(resp.Body) 82 if err != nil { 83 return nil, err 84 } 85 if gjson.ValidBytes(body) { 86 ret := gjson.ParseBytes(body) 87 if code := ret.Get("code"); code.Exists() && (code.Uint()/1e6 == 401) { 88 c.login() 89 return c.Call(url, req) 90 } 91 } 92 return body, err 93 } 94 95 func (c *client) call(url string, req *http.Request, token string) (*http.Response, error) { 96 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) 97 cli := &http.Client{} 98 return cli.Do(req) 99 } 100 101 func (c *client) getToken() string { 102 if t := c.token.Load(); t != nil { 103 return t.(string) 104 } 105 c.login() 106 return c.token.Load().(string) 107 } 108 109 type tokenResp struct { 110 Token string `json:"token"` 111 } 112 113 func (c *client) login() { 114 var userName, password string 115 fmt.Println(c.SelectTranslation(_userNameInput)) 116 if _, err := fmt.Scanln(&userName); err != nil { 117 c.logger.Panic(errors.Wrap(err, "failed to read username")) 118 } 119 120 fmt.Println(c.SelectTranslation(_userPasswordInput)) 121 p, err := term.ReadPassword(int(os.Stdin.Fd())) 122 if err != nil { 123 c.logger.Panic(errors.Wrap(err, "failed to read password")) 124 } 125 password = string(p) 126 127 body := fmt.Sprintf(`{"username":"%s","password":"%s"}`, userName, password) 128 url := fmt.Sprintf("%s/srv-applet-mgr/v0/login", c.cfg.Endpoint) 129 req, err := http.NewRequest("PUT", url, bytes.NewBuffer([]byte(body))) 130 if err != nil { 131 c.logger.Panic(errors.Wrap(err, "failed to create login request")) 132 } 133 req.Header.Set("Content-Type", "application/json") 134 cli := &http.Client{} 135 resp, err := cli.Do(req) 136 if err != nil { 137 c.logger.Panic(errors.Wrapf(err, "failed to login %s", url)) 138 } 139 defer resp.Body.Close() 140 141 ts := tokenResp{} 142 143 if err := json.NewDecoder(resp.Body).Decode(&ts); err != nil { 144 c.logger.Panic(errors.Wrap(err, "failed to decode login response")) 145 } 146 c.token.Store(ts.Token) 147 }