client.go

  1package ysweet
  2
  3import (
  4	"bytes"
  5	"encoding/json"
  6	"fmt"
  7	"net/http"
  8	"strings"
  9)
 10
 11type Client struct {
 12	baseURL string
 13}
 14
 15func NewClient(baseURL string) (*Client, error) {
 16	baseURL = strings.TrimSuffix(baseURL, "/")
 17	if baseURL == "" {
 18		return nil, fmt.Errorf("baseURL is required")
 19	}
 20	if !strings.HasPrefix(baseURL, "http://") && !strings.HasPrefix(baseURL, "https://") {
 21		return nil, fmt.Errorf("baseURL must start with http:// or https://")
 22	}
 23	return &Client{baseURL: baseURL}, nil
 24}
 25
 26type newDocRequest struct {
 27	DocID string `json:"docID"`
 28}
 29
 30type newDocResponse struct {
 31	DocID string `json:"docID"`
 32}
 33
 34type AuthDocResponse struct {
 35	URL           string `json:"url"`
 36	BaseURL       string `json:"baseUrl"`
 37	DocID         string `json:"docId"`
 38	Token         string `json:"token,omitempty"`
 39	Authorization string `json:"authorization,omitempty"`
 40}
 41
 42// Returns the actual WebsocketURL y-sweet expects a client to connect to, which is /d/{docID}/ws/{docID} for
 43// some unknown reason. Also includes token as query string, if defined.
 44func (a AuthDocResponse) WebsocketURL() string {
 45	if a.Token != "" {
 46		return fmt.Sprintf("%s/%s?token=%s", a.URL, a.DocID, a.Token)
 47	}
 48	return fmt.Sprintf("%s/%s", a.URL, a.DocID)
 49}
 50
 51func (c *Client) NewDoc(docID string) (string, error) {
 52	var body []byte
 53	var err error
 54	if docID != "" {
 55		body, err = json.Marshal(newDocRequest{DocID: docID})
 56		if err != nil {
 57			return "", fmt.Errorf("failed to marshal request: %w", err)
 58		}
 59	} else {
 60		body, err = json.Marshal(struct{}{})
 61		if err != nil {
 62			return "", fmt.Errorf("failed to marshal request: %w", err)
 63		}
 64	}
 65
 66	req, err := http.NewRequest(http.MethodPost, c.baseURL+"/doc/new", bytes.NewReader(body))
 67	if err != nil {
 68		return "", fmt.Errorf("failed to create request: %w", err)
 69	}
 70	req.Header.Set("Content-Type", "application/json")
 71
 72	resp, err := http.DefaultClient.Do(req)
 73	if err != nil {
 74		return "", fmt.Errorf("request failed: %w", err)
 75	}
 76	defer resp.Body.Close()
 77
 78	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
 79		return "", fmt.Errorf("server returned status %d", resp.StatusCode)
 80	}
 81
 82	var result newDocResponse
 83	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
 84		return "", fmt.Errorf("failed to decode response: %w", err)
 85	}
 86
 87	if docID != "" && result.DocID != docID {
 88		return "", fmt.Errorf("response docID %q does not match requested %q", result.DocID, docID)
 89	}
 90
 91	return result.DocID, nil
 92}
 93
 94func (c *Client) AuthDoc(docID string) (AuthDocResponse, error) {
 95	if docID == "" {
 96		return AuthDocResponse{}, fmt.Errorf("docID is required")
 97	}
 98
 99	req, err := http.NewRequest(http.MethodPost, c.baseURL+"/doc/"+docID+"/auth", nil)
100	if err != nil {
101		return AuthDocResponse{}, fmt.Errorf("failed to create request: %w", err)
102	}
103
104	resp, err := http.DefaultClient.Do(req)
105	if err != nil {
106		return AuthDocResponse{}, fmt.Errorf("request failed: %w", err)
107	}
108	defer resp.Body.Close()
109
110	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
111		return AuthDocResponse{}, fmt.Errorf("server returned status %d", resp.StatusCode)
112	}
113
114	var result AuthDocResponse
115	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
116		return AuthDocResponse{}, fmt.Errorf("failed to decode response: %w", err)
117	}
118
119	return result, nil
120}