feat: initial commit
This commit is contained in:
281
pkg/http/blob.go
Normal file
281
pkg/http/blob.go
Normal file
@ -0,0 +1,281 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/fs"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/bus"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module"
|
||||
"forge.cadoles.com/arcad/edge/pkg/storage"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
errorCodeForbidden = "forbidden"
|
||||
errorCodeInternalError = "internal-error"
|
||||
errorCodeBadRequest = "bad-request"
|
||||
errorCodeNotFound = "not-found"
|
||||
)
|
||||
|
||||
type uploadResponse struct {
|
||||
Bucket string `json:"bucket"`
|
||||
BlobID storage.BlobID `json:"blobId"`
|
||||
}
|
||||
|
||||
func (h *Handler) handleAppUpload(w http.ResponseWriter, r *http.Request) {
|
||||
h.mutex.RLock()
|
||||
defer h.mutex.RUnlock()
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
r.Body = http.MaxBytesReader(w, r.Body, h.uploadMaxFileSize)
|
||||
|
||||
if err := r.ParseMultipartForm(h.uploadMaxFileSize); err != nil {
|
||||
logger.Error(ctx, "could not parse multipart form", logger.E(errors.WithStack(err)))
|
||||
jsonError(w, http.StatusBadRequest, errorCodeBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
_, fileHeader, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not read form file", logger.E(errors.WithStack(err)))
|
||||
jsonError(w, http.StatusBadRequest, errorCodeBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var metadata map[string]any
|
||||
|
||||
rawMetadata := r.Form.Get("metadata")
|
||||
if rawMetadata != "" {
|
||||
if err := json.Unmarshal([]byte(rawMetadata), &metadata); err != nil {
|
||||
logger.Error(ctx, "could not parse metadata", logger.E(errors.WithStack(err)))
|
||||
jsonError(w, http.StatusBadRequest, errorCodeBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx = module.WithContext(ctx, map[module.ContextKey]any{
|
||||
module.ContextKeyOriginRequest: r,
|
||||
})
|
||||
|
||||
requestMsg := module.NewMessageUploadRequest(ctx, fileHeader, metadata)
|
||||
|
||||
reply, err := h.bus.Request(ctx, requestMsg)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not retrieve file", logger.E(errors.WithStack(err)))
|
||||
jsonError(w, http.StatusInternalServerError, errorCodeInternalError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug(ctx, "upload reply", logger.F("reply", reply))
|
||||
|
||||
responseMsg, ok := reply.(*module.MessageUploadResponse)
|
||||
if !ok {
|
||||
logger.Error(
|
||||
ctx, "unexpected upload response message",
|
||||
logger.F("message", reply),
|
||||
)
|
||||
jsonError(w, http.StatusInternalServerError, errorCodeInternalError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if !responseMsg.Allow {
|
||||
jsonError(w, http.StatusForbidden, errorCodeForbidden)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
encoder := json.NewEncoder(w)
|
||||
res := &uploadResponse{
|
||||
Bucket: responseMsg.Bucket,
|
||||
BlobID: responseMsg.BlobID,
|
||||
}
|
||||
|
||||
if err := encoder.Encode(res); err != nil {
|
||||
panic(errors.Wrap(err, "could not encode upload response"))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) handleAppDownload(w http.ResponseWriter, r *http.Request) {
|
||||
h.mutex.RLock()
|
||||
defer h.mutex.RUnlock()
|
||||
|
||||
bucket := chi.URLParam(r, "bucket")
|
||||
blobID := chi.URLParam(r, "blobID")
|
||||
|
||||
ctx := logger.With(r.Context(), logger.F("blobID", blobID), logger.F("bucket", bucket))
|
||||
ctx = module.WithContext(ctx, map[module.ContextKey]any{
|
||||
module.ContextKeyOriginRequest: r,
|
||||
})
|
||||
|
||||
requestMsg := module.NewMessageDownloadRequest(ctx, bucket, storage.BlobID(blobID))
|
||||
|
||||
reply, err := h.bus.Request(ctx, requestMsg)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not retrieve file", logger.E(errors.WithStack(err)))
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
replyMsg, ok := reply.(*module.MessageDownloadResponse)
|
||||
if !ok {
|
||||
logger.Error(
|
||||
ctx, "unexpected download response message",
|
||||
logger.E(errors.WithStack(bus.ErrUnexpectedMessage)),
|
||||
logger.F("message", reply),
|
||||
)
|
||||
jsonError(w, http.StatusInternalServerError, errorCodeInternalError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if !replyMsg.Allow {
|
||||
jsonError(w, http.StatusForbidden, errorCodeForbidden)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if replyMsg.Blob == nil {
|
||||
jsonError(w, http.StatusNotFound, errorCodeNotFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := replyMsg.Blob.Close(); err != nil {
|
||||
logger.Error(ctx, "could not close blob", logger.E(errors.WithStack(err)))
|
||||
}
|
||||
}()
|
||||
|
||||
http.ServeContent(w, r, string(replyMsg.BlobInfo.ID()), replyMsg.BlobInfo.ModTime(), replyMsg.Blob)
|
||||
}
|
||||
|
||||
func serveFile(w http.ResponseWriter, r *http.Request, fs fs.FS, path string) {
|
||||
ctx := logger.With(r.Context(), logger.F("path", path))
|
||||
|
||||
file, err := fs.Open(path)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "error while opening fs file", logger.E(errors.WithStack(err)))
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := file.Close(); err != nil {
|
||||
logger.Error(ctx, "error while closing fs file", logger.E(errors.WithStack(err)))
|
||||
}
|
||||
}()
|
||||
|
||||
info, err := file.Stat()
|
||||
if err != nil {
|
||||
logger.Error(ctx, "error while retrieving fs file stat", logger.E(errors.WithStack(err)))
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
reader, ok := file.(io.ReadSeeker)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
http.ServeContent(w, r, path, info.ModTime(), reader)
|
||||
}
|
||||
|
||||
type jsonErrorResponse struct {
|
||||
Error jsonErr `json:"error"`
|
||||
}
|
||||
|
||||
type jsonErr struct {
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
func jsonError(w http.ResponseWriter, status int, code string) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
|
||||
encoder := json.NewEncoder(w)
|
||||
response := jsonErrorResponse{
|
||||
Error: jsonErr{
|
||||
Code: code,
|
||||
},
|
||||
}
|
||||
|
||||
if err := encoder.Encode(response); err != nil {
|
||||
panic(errors.WithStack(err))
|
||||
}
|
||||
}
|
||||
|
||||
type uploadedFile struct {
|
||||
multipart.File
|
||||
header *multipart.FileHeader
|
||||
modTime time.Time
|
||||
}
|
||||
|
||||
// Stat implements fs.File
|
||||
func (f *uploadedFile) Stat() (fs.FileInfo, error) {
|
||||
return &uploadedFileInfo{
|
||||
header: f.header,
|
||||
modTime: f.modTime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type uploadedFileInfo struct {
|
||||
header *multipart.FileHeader
|
||||
modTime time.Time
|
||||
}
|
||||
|
||||
// IsDir implements fs.FileInfo
|
||||
func (i *uploadedFileInfo) IsDir() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ModTime implements fs.FileInfo
|
||||
func (i *uploadedFileInfo) ModTime() time.Time {
|
||||
return i.modTime
|
||||
}
|
||||
|
||||
// Mode implements fs.FileInfo
|
||||
func (i *uploadedFileInfo) Mode() fs.FileMode {
|
||||
return os.ModePerm
|
||||
}
|
||||
|
||||
// Name implements fs.FileInfo
|
||||
func (i *uploadedFileInfo) Name() string {
|
||||
return i.header.Filename
|
||||
}
|
||||
|
||||
// Size implements fs.FileInfo
|
||||
func (i *uploadedFileInfo) Size() int64 {
|
||||
return i.header.Size
|
||||
}
|
||||
|
||||
// Sys implements fs.FileInfo
|
||||
func (i *uploadedFileInfo) Sys() any {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ fs.File = &uploadedFile{}
|
||||
_ fs.FileInfo = &uploadedFileInfo{}
|
||||
)
|
22
pkg/http/client.go
Normal file
22
pkg/http/client.go
Normal file
@ -0,0 +1,22 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/sdk"
|
||||
)
|
||||
|
||||
func (h *Handler) handleSDKClient(w http.ResponseWriter, r *http.Request) {
|
||||
serveFile(w, r, &sdk.FS, "client/dist/client.js")
|
||||
}
|
||||
|
||||
func (h *Handler) handleSDKClientMap(w http.ResponseWriter, r *http.Request) {
|
||||
serveFile(w, r, &sdk.FS, "client/dist/client.js.map")
|
||||
}
|
||||
|
||||
func (h *Handler) handleAppFiles(w http.ResponseWriter, r *http.Request) {
|
||||
h.mutex.RLock()
|
||||
defer h.mutex.RUnlock()
|
||||
|
||||
h.public.ServeHTTP(w, r)
|
||||
}
|
114
pkg/http/handler.go
Normal file
114
pkg/http/handler.go
Normal file
@ -0,0 +1,114 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||
"forge.cadoles.com/arcad/edge/pkg/bundle"
|
||||
"forge.cadoles.com/arcad/edge/pkg/bus"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/igm/sockjs-go/v3/sockjs"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
sockJSPathPrefix = "/edge/sock"
|
||||
serverMainScript = "server/main.js"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
bundle bundle.Bundle
|
||||
public http.Handler
|
||||
router chi.Router
|
||||
|
||||
sockjs http.Handler
|
||||
bus bus.Bus
|
||||
sockjsOpts sockjs.Options
|
||||
uploadMaxFileSize int64
|
||||
|
||||
server *app.Server
|
||||
serverModuleFactories []app.ServerModuleFactory
|
||||
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.router.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) Load(bdle bundle.Bundle) error {
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
|
||||
file, _, err := bdle.File(serverMainScript)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not open server main script")
|
||||
}
|
||||
|
||||
mainScript, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not read server main script")
|
||||
}
|
||||
|
||||
server := app.NewServer(h.serverModuleFactories...)
|
||||
|
||||
if err := server.Load(serverMainScript, string(mainScript)); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
fs := bundle.NewFileSystem("public", bdle)
|
||||
public := http.FileServer(fs)
|
||||
sockjs := sockjs.NewHandler(sockJSPathPrefix, h.sockjsOpts, h.handleSockJSSession)
|
||||
|
||||
if h.server != nil {
|
||||
h.server.Stop()
|
||||
}
|
||||
|
||||
if err := server.Start(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
h.bundle = bdle
|
||||
h.server = server
|
||||
h.public = public
|
||||
h.sockjs = sockjs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewHandler(funcs ...HandlerOptionFunc) *Handler {
|
||||
opts := defaultHandlerOptions()
|
||||
for _, fn := range funcs {
|
||||
fn(opts)
|
||||
}
|
||||
|
||||
router := chi.NewRouter()
|
||||
|
||||
handler := &Handler{
|
||||
uploadMaxFileSize: opts.UploadMaxFileSize,
|
||||
sockjsOpts: opts.SockJS,
|
||||
router: router,
|
||||
serverModuleFactories: opts.ServerModuleFactories,
|
||||
bus: opts.Bus,
|
||||
}
|
||||
|
||||
router.Route("/edge", func(r chi.Router) {
|
||||
r.Route("/sdk", func(r chi.Router) {
|
||||
r.Get("/client.js", handler.handleSDKClient)
|
||||
r.Get("/client.js.map", handler.handleSDKClientMap)
|
||||
})
|
||||
|
||||
r.Route("/api/v1", func(r chi.Router) {
|
||||
r.Post("/upload", handler.handleAppUpload)
|
||||
r.Get("/download/{bucket}/{blobID}", handler.handleAppDownload)
|
||||
})
|
||||
|
||||
r.HandleFunc("/sock/*", handler.handleSockJS)
|
||||
})
|
||||
|
||||
router.Get("/*", handler.handleAppFiles)
|
||||
|
||||
return handler
|
||||
}
|
57
pkg/http/options.go
Normal file
57
pkg/http/options.go
Normal file
@ -0,0 +1,57 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||
"forge.cadoles.com/arcad/edge/pkg/bus"
|
||||
"forge.cadoles.com/arcad/edge/pkg/bus/memory"
|
||||
"github.com/igm/sockjs-go/v3/sockjs"
|
||||
)
|
||||
|
||||
type HandlerOptions struct {
|
||||
Bus bus.Bus
|
||||
SockJS sockjs.Options
|
||||
ServerModuleFactories []app.ServerModuleFactory
|
||||
UploadMaxFileSize int64
|
||||
}
|
||||
|
||||
func defaultHandlerOptions() *HandlerOptions {
|
||||
sockjsOptions := func() sockjs.Options {
|
||||
return sockjs.DefaultOptions
|
||||
}()
|
||||
sockjsOptions.DisconnectDelay = 10 * time.Second
|
||||
|
||||
return &HandlerOptions{
|
||||
Bus: memory.NewBus(),
|
||||
SockJS: sockjsOptions,
|
||||
ServerModuleFactories: make([]app.ServerModuleFactory, 0),
|
||||
UploadMaxFileSize: 1024 * 10, // 10Mb
|
||||
}
|
||||
}
|
||||
|
||||
type HandlerOptionFunc func(*HandlerOptions)
|
||||
|
||||
func WithServerModules(factories ...app.ServerModuleFactory) HandlerOptionFunc {
|
||||
return func(opts *HandlerOptions) {
|
||||
opts.ServerModuleFactories = factories
|
||||
}
|
||||
}
|
||||
|
||||
func WithSockJS(options sockjs.Options) HandlerOptionFunc {
|
||||
return func(opts *HandlerOptions) {
|
||||
opts.SockJS = options
|
||||
}
|
||||
}
|
||||
|
||||
func WithBus(bus bus.Bus) HandlerOptionFunc {
|
||||
return func(opts *HandlerOptions) {
|
||||
opts.Bus = bus
|
||||
}
|
||||
}
|
||||
|
||||
func WithUploadMaxFileSize(size int64) HandlerOptionFunc {
|
||||
return func(opts *HandlerOptions) {
|
||||
opts.UploadMaxFileSize = size
|
||||
}
|
||||
}
|
233
pkg/http/sockjs.go
Normal file
233
pkg/http/sockjs.go
Normal file
@ -0,0 +1,233 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/module"
|
||||
"github.com/igm/sockjs-go/v3/sockjs"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
statusChannelClosed = iota
|
||||
)
|
||||
|
||||
func (h *Handler) handleSockJS(w http.ResponseWriter, r *http.Request) {
|
||||
h.mutex.RLock()
|
||||
defer h.mutex.RUnlock()
|
||||
|
||||
h.sockjs.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) handleSockJSSession(sess sockjs.Session) {
|
||||
ctx := logger.With(sess.Request().Context(),
|
||||
logger.F("sessionID", sess.ID()),
|
||||
)
|
||||
|
||||
logger.Debug(ctx, "new sockjs session")
|
||||
|
||||
defer func() {
|
||||
if sess.GetSessionState() == sockjs.SessionActive {
|
||||
if err := sess.Close(statusChannelClosed, "channel closed"); err != nil {
|
||||
logger.Error(ctx, "could not close sockjs session", logger.E(errors.WithStack(err)))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go h.handleServerMessages(ctx, sess)
|
||||
h.handleClientMessages(ctx, sess)
|
||||
}
|
||||
|
||||
func (h *Handler) handleServerMessages(ctx context.Context, sess sockjs.Session) {
|
||||
messages, err := h.bus.Subscribe(ctx, module.MessageNamespaceServer)
|
||||
if err != nil {
|
||||
panic(errors.WithStack(err))
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// Close messages subscriber
|
||||
h.bus.Unsubscribe(ctx, module.MessageNamespaceServer, messages)
|
||||
|
||||
logger.Debug(ctx, "unsubscribed")
|
||||
|
||||
if sess.GetSessionState() != sockjs.SessionActive {
|
||||
return
|
||||
}
|
||||
|
||||
if err := sess.Close(statusChannelClosed, "channel closed"); err != nil {
|
||||
logger.Error(ctx, "could not close sockjs session", logger.E(errors.WithStack(err)))
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
||||
case msg := <-messages:
|
||||
serverMessage, ok := msg.(*module.ServerMessage)
|
||||
if !ok {
|
||||
logger.Error(
|
||||
ctx,
|
||||
"unexpected server message",
|
||||
logger.F("message", msg),
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
sessionID := module.ContextValue[string](serverMessage.Context, module.ContextKeySessionID)
|
||||
|
||||
isDest := sessionID == "" || sessionID == sess.ID()
|
||||
if !isDest {
|
||||
continue
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(serverMessage.Data)
|
||||
if err != nil {
|
||||
logger.Error(
|
||||
ctx,
|
||||
"could not encode message",
|
||||
logger.E(err),
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
message := NewWebsocketMessage(
|
||||
WebsocketMessageTypeMessage,
|
||||
json.RawMessage(payload),
|
||||
)
|
||||
|
||||
data, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
logger.Error(
|
||||
ctx,
|
||||
"could not encode message",
|
||||
logger.E(err),
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Debug(ctx, "sending message")
|
||||
|
||||
// Send message
|
||||
if err := sess.Send(string(data)); err != nil {
|
||||
logger.Error(
|
||||
ctx,
|
||||
"could not send message",
|
||||
logger.E(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) handleClientMessages(ctx context.Context, sess sockjs.Session) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
logger.Debug(ctx, "context done")
|
||||
|
||||
return
|
||||
|
||||
default:
|
||||
logger.Debug(ctx, "waiting for websocket data")
|
||||
|
||||
data, err := sess.RecvCtx(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, sockjs.ErrSessionNotOpen) {
|
||||
break
|
||||
}
|
||||
|
||||
logger.Error(
|
||||
ctx,
|
||||
"could not read message",
|
||||
logger.E(errors.WithStack(err)),
|
||||
)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
logger.Debug(ctx, "websocket data received", logger.F("data", data))
|
||||
|
||||
message := &WebsocketMessage{}
|
||||
if err := json.Unmarshal([]byte(data), message); err != nil {
|
||||
logger.Error(
|
||||
ctx,
|
||||
"could not decode message",
|
||||
logger.E(errors.WithStack(err)),
|
||||
)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
switch {
|
||||
|
||||
case message.Type == WebsocketMessageTypeMessage:
|
||||
var payload map[string]interface{}
|
||||
if err := json.Unmarshal(message.Payload, &payload); err != nil {
|
||||
logger.Error(
|
||||
ctx,
|
||||
"could not decode payload",
|
||||
logger.E(errors.WithStack(err)),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctx := logger.With(ctx, logger.F("payload", payload))
|
||||
ctx = module.WithContext(ctx, map[module.ContextKey]any{
|
||||
module.ContextKeySessionID: sess.ID(),
|
||||
module.ContextKeyOriginRequest: sess.Request(),
|
||||
})
|
||||
|
||||
clientMessage := module.NewClientMessage(ctx, payload)
|
||||
|
||||
logger.Debug(ctx, "publishing new client message", logger.F("message", clientMessage))
|
||||
|
||||
if err := h.bus.Publish(ctx, clientMessage); err != nil {
|
||||
logger.Error(ctx, "could not publish message",
|
||||
logger.E(errors.WithStack(err)),
|
||||
logger.F("message", clientMessage),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug(ctx, "new client message published", logger.F("message", clientMessage))
|
||||
|
||||
default:
|
||||
logger.Error(
|
||||
ctx,
|
||||
"unsupported message type",
|
||||
logger.F("messageType", message.Type),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
WebsocketMessageTypeMessage = "message"
|
||||
)
|
||||
|
||||
type WebsocketMessage struct {
|
||||
Type string `json:"t"`
|
||||
Payload json.RawMessage `json:"p"`
|
||||
}
|
||||
|
||||
type WebsocketMessagePayload struct {
|
||||
Data map[string]interface{} `json:"d"`
|
||||
}
|
||||
|
||||
func NewWebsocketMessage(dataType string, payload json.RawMessage) *WebsocketMessage {
|
||||
return &WebsocketMessage{
|
||||
Type: dataType,
|
||||
Payload: payload,
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user