coder/codersdk/serversentevents.go

96 lines
2.1 KiB
Go

package codersdk
import (
"bufio"
"context"
"fmt"
"io"
"strings"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd/tracing"
)
type ServerSentEvent struct {
Type ServerSentEventType `json:"type"`
Data interface{} `json:"data"`
}
type ServerSentEventType string
const (
ServerSentEventTypePing ServerSentEventType = "ping"
ServerSentEventTypeData ServerSentEventType = "data"
ServerSentEventTypeError ServerSentEventType = "error"
)
func ServerSentEventReader(ctx context.Context, rc io.ReadCloser) func() (*ServerSentEvent, error) {
_, span := tracing.StartSpan(ctx)
defer span.End()
reader := bufio.NewReader(rc)
nextLineValue := func(prefix string) ([]byte, error) {
var (
line string
err error
)
for {
line, err = reader.ReadString('\n')
if err != nil {
return nil, xerrors.Errorf("reading next string: %w", err)
}
if strings.TrimSpace(line) != "" {
break
}
}
if !strings.HasPrefix(line, fmt.Sprintf("%s: ", prefix)) {
return nil, xerrors.Errorf("expecting %s prefix, got: %s", prefix, line)
}
s := strings.TrimPrefix(line, fmt.Sprintf("%s: ", prefix))
s = strings.TrimSpace(s)
return []byte(s), nil
}
nextEvent := func() (*ServerSentEvent, error) {
for {
t, err := nextLineValue("event")
if err != nil {
return nil, xerrors.Errorf("reading next line value: %w", err)
}
switch ServerSentEventType(t) {
case ServerSentEventTypePing:
return &ServerSentEvent{
Type: ServerSentEventTypePing,
}, nil
case ServerSentEventTypeData:
d, err := nextLineValue("data")
if err != nil {
return nil, xerrors.Errorf("reading next line value: %w", err)
}
return &ServerSentEvent{
Type: ServerSentEventTypeData,
Data: d,
}, nil
case ServerSentEventTypeError:
d, err := nextLineValue("data")
if err != nil {
return nil, xerrors.Errorf("reading next line value: %w", err)
}
return &ServerSentEvent{
Type: ServerSentEventTypeError,
Data: d,
}, nil
default:
return nil, xerrors.Errorf("unknown event type: %s", t)
}
}
}
return nextEvent
}