Files
dragonchain-prime-sdk-go/client/client.go
2025-11-10 16:44:59 -05:00

145 lines
3.5 KiB
Go

package client
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
)
type Client struct {
publicID string
authKeyID string
authKey string
baseURL string
httpClient *http.Client
}
func NewClient(publicID, authKeyID, authKey, baseURL string) *Client {
return &Client{
publicID: publicID,
authKeyID: authKeyID,
authKey: authKey,
baseURL: strings.TrimSuffix(baseURL, "/"),
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}
func (c *Client) generateAuthHeader(method, path, timestamp, contentType string, body []byte) string {
msgStr := c.hmacMessage(method, path, timestamp, contentType, body)
hmacMsg := c.createHmac(msgStr)
b64hmac := base64.StdEncoding.EncodeToString(hmacMsg)
return fmt.Sprintf("DC1-HMAC-SHA256 %s:%s", c.authKeyID, b64hmac)
}
func (c *Client) hmacMessage(method, path, timestamp, contentType string, body []byte) string {
h := sha256.New()
h.Write(body)
hashContent := h.Sum(nil)
b64Content := base64.StdEncoding.EncodeToString(hashContent)
return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s",
strings.ToUpper(method),
path,
c.publicID,
timestamp,
contentType,
b64Content,
)
}
func (c *Client) createHmac(msgStr string) []byte {
h := hmac.New(sha256.New, []byte(c.authKey))
h.Write([]byte(msgStr))
return h.Sum(nil)
}
func (c *Client) doRequest(method, path, contentType string, body any, response any) error {
var bodyBytes []byte
var err error
if body != nil {
if !checkByteSlice(body) {
bodyBytes, err = json.Marshal(body)
if err != nil {
return fmt.Errorf("failed to marshal request body: %w", err)
}
contentType = "application/json"
} else {
bodyBytes = body.([]byte)
}
}
if len(bodyBytes) == 0 {
bodyBytes = []byte("")
}
fullURL := c.baseURL + path
req, err := http.NewRequest(method, fullURL, bytes.NewBuffer(bodyBytes))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
timestamp := fmt.Sprintf("%d", time.Now().Unix())
authHeader := c.generateAuthHeader(method, path, timestamp, contentType, bodyBytes)
req.Header.Set("Authorization", authHeader)
req.Header.Set("Dragonchain", c.publicID)
req.Header.Set("Timestamp", timestamp)
if contentType != "" {
req.Header.Set("Content-Type", contentType)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("failed to execute request: %w", err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response body: %w", err)
}
if resp.StatusCode >= 400 {
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, strings.TrimSpace(string(respBody)))
}
if response != nil && len(respBody) > 0 {
if err := json.Unmarshal(respBody, response); err != nil {
return fmt.Errorf("failed to unmarshal response: %w", err)
}
}
return nil
}
func checkByteSlice(body interface{}) bool {
_, ok := body.([]byte)
return ok
}
func (c *Client) Get(path string, response any) error {
return c.doRequest(http.MethodGet, path, "", nil, response)
}
func (c *Client) Post(path, contentType string, body any, response any) error {
return c.doRequest(http.MethodPost, path, contentType, body, response)
}
func (c *Client) Put(path, contentType string, body any, response any) error {
return c.doRequest(http.MethodPut, path, contentType, body, response)
}
func (c *Client) Delete(path string, response any) error {
return c.doRequest(http.MethodDelete, path, "", nil, response)
}