2024-08-21 02:01:29 +02:00
|
|
|
// Copyright 2024, Command Line Inc.
|
2024-08-09 03:24:54 +02:00
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
package wcloud
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2024-09-05 23:25:45 +02:00
|
|
|
"github.com/wavetermdev/waveterm/pkg/telemetry"
|
|
|
|
"github.com/wavetermdev/waveterm/pkg/util/daystr"
|
|
|
|
"github.com/wavetermdev/waveterm/pkg/wavebase"
|
2024-08-09 03:24:54 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
const WCloudEndpoint = "https://api.waveterm.dev/central"
|
|
|
|
const WCloudEndpointVarName = "WCLOUD_ENDPOINT"
|
|
|
|
const WCloudWSEndpoint = "wss://wsapi.waveterm.dev/"
|
|
|
|
const WCloudWSEndpointVarName = "WCLOUD_WS_ENDPOINT"
|
|
|
|
|
2024-10-27 21:12:41 +01:00
|
|
|
var WCloudWSEndpoint_VarCache string
|
|
|
|
var WCloudEndpoint_VarCache string
|
|
|
|
|
2024-08-09 03:24:54 +02:00
|
|
|
const APIVersion = 1
|
|
|
|
const MaxPtyUpdateSize = (128 * 1024)
|
|
|
|
const MaxUpdatesPerReq = 10
|
|
|
|
const MaxUpdatesToDeDup = 1000
|
|
|
|
const MaxUpdateWriterErrors = 3
|
|
|
|
const WCloudDefaultTimeout = 5 * time.Second
|
|
|
|
const WCloudWebShareUpdateTimeout = 15 * time.Second
|
|
|
|
|
|
|
|
// setting to 1M to be safe (max is 6M for API-GW + Lambda, but there is base64 encoding and upload time)
|
|
|
|
// we allow one extra update past this estimated size
|
|
|
|
const MaxUpdatePayloadSize = 1 * (1024 * 1024)
|
|
|
|
|
|
|
|
const TelemetryUrl = "/telemetry"
|
|
|
|
const NoTelemetryUrl = "/no-telemetry"
|
|
|
|
const WebShareUpdateUrl = "/auth/web-share-update"
|
|
|
|
|
2024-10-27 21:12:41 +01:00
|
|
|
func CacheAndRemoveEnvVars() error {
|
|
|
|
WCloudEndpoint_VarCache = os.Getenv(WCloudEndpointVarName)
|
|
|
|
err := checkEndpointVar(WCloudEndpoint_VarCache, "wcloud endpoint", WCloudEndpointVarName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
os.Unsetenv(WCloudEndpointVarName)
|
|
|
|
WCloudWSEndpoint_VarCache = os.Getenv(WCloudWSEndpointVarName)
|
|
|
|
err = checkWSEndpointVar(WCloudWSEndpoint_VarCache, "wcloud ws endpoint", WCloudWSEndpointVarName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
os.Unsetenv(WCloudWSEndpointVarName)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkEndpointVar(endpoint string, debugName string, varName string) error {
|
2024-08-09 03:24:54 +02:00
|
|
|
if !wavebase.IsDevMode() {
|
2024-10-27 21:12:41 +01:00
|
|
|
return nil
|
2024-08-09 03:24:54 +02:00
|
|
|
}
|
|
|
|
if endpoint == "" || !strings.HasPrefix(endpoint, "https://") {
|
2024-10-27 21:12:41 +01:00
|
|
|
return fmt.Errorf("invalid %s, %s not set or invalid", debugName, varName)
|
2024-08-09 03:24:54 +02:00
|
|
|
}
|
2024-10-27 21:12:41 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkWSEndpointVar(endpoint string, debugName string, varName string) error {
|
|
|
|
if !wavebase.IsDevMode() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
log.Printf("checking endpoint %q\n", endpoint)
|
|
|
|
if endpoint == "" || !strings.HasPrefix(endpoint, "wss://") {
|
|
|
|
return fmt.Errorf("invalid %s, %s not set or invalid", debugName, varName)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetEndpoint() string {
|
|
|
|
if !wavebase.IsDevMode() {
|
|
|
|
return WCloudEndpoint
|
|
|
|
}
|
|
|
|
endpoint := WCloudEndpoint_VarCache
|
2024-08-09 03:24:54 +02:00
|
|
|
return endpoint
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetWSEndpoint() string {
|
|
|
|
if !wavebase.IsDevMode() {
|
|
|
|
return WCloudWSEndpoint
|
|
|
|
}
|
2024-10-27 21:12:41 +01:00
|
|
|
endpoint := WCloudWSEndpoint_VarCache
|
2024-08-09 03:24:54 +02:00
|
|
|
return endpoint
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeAnonPostReq(ctx context.Context, apiUrl string, data interface{}) (*http.Request, error) {
|
|
|
|
endpoint := GetEndpoint()
|
|
|
|
if endpoint == "" {
|
|
|
|
return nil, errors.New("wcloud endpoint not set")
|
|
|
|
}
|
|
|
|
var dataReader io.Reader
|
|
|
|
if data != nil {
|
|
|
|
byteArr, err := json.Marshal(data)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error marshaling json for %s request: %v", apiUrl, err)
|
|
|
|
}
|
|
|
|
dataReader = bytes.NewReader(byteArr)
|
|
|
|
}
|
|
|
|
fullUrl := GetEndpoint() + apiUrl
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "POST", fullUrl, dataReader)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error creating %s request: %v", apiUrl, err)
|
|
|
|
}
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
req.Header.Set("X-PromptAPIVersion", strconv.Itoa(APIVersion))
|
|
|
|
req.Header.Set("X-PromptAPIUrl", apiUrl)
|
|
|
|
req.Close = true
|
|
|
|
return req, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func doRequest(req *http.Request, outputObj interface{}) (*http.Response, error) {
|
|
|
|
apiUrl := req.Header.Get("X-PromptAPIUrl")
|
|
|
|
log.Printf("[wcloud] sending request %s %v\n", req.Method, req.URL)
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error contacting wcloud %q service: %v", apiUrl, err)
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
bodyBytes, err := io.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
return resp, fmt.Errorf("error reading %q response body: %v", apiUrl, err)
|
|
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
return resp, fmt.Errorf("error contacting wcloud %q service: %s", apiUrl, resp.Status)
|
|
|
|
}
|
|
|
|
if outputObj != nil && resp.Header.Get("Content-Type") == "application/json" {
|
|
|
|
err = json.Unmarshal(bodyBytes, outputObj)
|
|
|
|
if err != nil {
|
|
|
|
return resp, fmt.Errorf("error decoding json: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func SendTelemetry(ctx context.Context, clientId string) error {
|
|
|
|
if !telemetry.IsTelemetryEnabled() {
|
|
|
|
log.Printf("telemetry disabled, not sending\n")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
activity, err := telemetry.GetNonUploadedActivity(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot get activity: %v", err)
|
|
|
|
}
|
|
|
|
if len(activity) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
log.Printf("[wcloud] sending telemetry data\n")
|
|
|
|
dayStr := daystr.GetCurDayStr()
|
2024-09-19 20:57:40 +02:00
|
|
|
input := TelemetryInputType{
|
|
|
|
ClientId: clientId,
|
|
|
|
UserId: clientId,
|
|
|
|
AppType: "w2",
|
|
|
|
AutoUpdateEnabled: telemetry.IsAutoUpdateEnabled(),
|
|
|
|
AutoUpdateChannel: telemetry.AutoUpdateChannel(),
|
|
|
|
CurDay: dayStr,
|
|
|
|
Activity: activity,
|
|
|
|
}
|
2024-08-09 03:24:54 +02:00
|
|
|
req, err := makeAnonPostReq(ctx, TelemetryUrl, input)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, err = doRequest(req, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = telemetry.MarkActivityAsUploaded(ctx, activity)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error marking activity as uploaded: %v", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func SendNoTelemetryUpdate(ctx context.Context, clientId string, noTelemetryVal bool) error {
|
|
|
|
req, err := makeAnonPostReq(ctx, NoTelemetryUrl, NoTelemetryInputType{ClientId: clientId, Value: noTelemetryVal})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, err = doRequest(req, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|