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  }