Add webUI into binary

To get the most simple deployment of excalidraw, the binary should ship
all components (webUI, socket.io, data storage).
This commit is contained in:
patwie
2024-03-29 09:39:22 +00:00
parent 36e3ecb5c7
commit 2dd1421b6e
7 changed files with 74 additions and 39 deletions
+3 -1
View File
@@ -1,2 +1,4 @@
dist/ dist/
excalidraw-backend frontend/
!frontend/.keep
excalidraw-complete
-3
View File
@@ -46,6 +46,3 @@ changelog:
- "^docs:" - "^docs:"
- "^test:" - "^test:"
release:
disable: true
skip_upload: true
+9 -4
View File
@@ -1,12 +1,17 @@
# Exalidraw Backend # Exalidraw Complete
Frustrated on how difficult it is to setup excalidraw self-hosted but with data Frustrated on how difficult it is to setup excalidraw self-hosted but with data
storage and collaboration function this represents and attempt to run the storage and collaboration function this represents and attempt to run the
necessary function with a single binary implemented in go. necessary function with a single binary implemented in go. This includes:
Apply the patch to the frontend and build excalidraw. Run Excalidraw frontend and - the frontend UI
on the same host run - a in-memory data layer
- socket.io implementation for collaboration
Apply the patch to the frontend and build excalidraw into `frontend`. Run
```bash ```bash
go run main.go go run main.go
``` ```
Everything will be served under `localhost:3002`
+1 -1
View File
@@ -2,7 +2,7 @@ package memory
import ( import (
"context" "context"
"excalidraw-backend/core" "excalidraw-complete/core"
"fmt" "fmt"
"github.com/oklog/ulid/v2" "github.com/oklog/ulid/v2"
View File
+1 -1
View File
@@ -1,4 +1,4 @@
module excalidraw-backend module excalidraw-complete
go 1.21 go 1.21
+60 -29
View File
@@ -2,20 +2,24 @@ package main
import ( import (
"bytes" "bytes"
"excalidraw-backend/core" "embed"
"excalidraw-backend/documents/memory" _ "embed"
"excalidraw-complete/core"
"excalidraw-complete/documents/memory"
"fmt" "fmt"
"io" "io"
"io/fs"
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
"path"
"strings"
"syscall" "syscall"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware" "github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors" "github.com/go-chi/cors"
"github.com/go-chi/render" "github.com/go-chi/render"
"github.com/oklog/ulid/v2"
"github.com/zishang520/engine.io/v2/types" "github.com/zishang520/engine.io/v2/types"
socketio "github.com/zishang520/socket.io/v2/socket" socketio "github.com/zishang520/socket.io/v2/socket"
) )
@@ -35,6 +39,35 @@ type (
} }
) )
//go:embed all:frontend
var assets embed.FS
func Assets() (fs.FS, error) {
return fs.Sub(assets, "frontend")
}
type FrontEndHandler struct{}
func (h FrontEndHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
frontendHandler(w, r)
}
func frontendHandler(w http.ResponseWriter, r *http.Request) {
upath := r.URL.Path
if !strings.HasPrefix(upath, "/") {
upath = "/" + upath
r.URL.Path = upath
}
upath = path.Clean(upath)
sub, err := fs.Sub(assets, "frontend")
if err != nil {
panic(err)
}
http.FileServer(http.FS(sub)).ServeHTTP(w, r)
}
func main() { func main() {
opts := socketio.DefaultServerOptions() opts := socketio.DefaultServerOptions()
opts.SetMaxHttpBufferSize(5000000) opts.SetMaxHttpBufferSize(5000000)
@@ -53,24 +86,23 @@ func main() {
room := socketio.Room(datas[0].(string)) room := socketio.Room(datas[0].(string))
fmt.Printf("Socket %v has joined %v\n", me, room) fmt.Printf("Socket %v has joined %v\n", me, room)
socket.Join(room) socket.Join(room)
ioo.In(room).FetchSockets()(func(sockets []*socketio.RemoteSocket, _ error) { ioo.In(room).FetchSockets()(func(usersInRoom []*socketio.RemoteSocket, _ error) {
if len(usersInRoom) <= 1 {
if len(sockets) <= 1 { ioo.To(socketio.Room(me)).Emit("first-in-room")
ioo.To(socketio.Room(socket.Id())).Emit("first-in-room")
} else { } else {
fmt.Printf("emit new user %v in room %v\n", me, room) fmt.Printf("emit new user %v in room %v\n", me, room)
socket.Broadcast().To(room).Emit("new-user", me) socket.Broadcast().To(room).Emit("new-user", me)
} }
data := []socketio.SocketId{} // Inform all clients by new users.
for _, osocket := range sockets { newRoomUsers := []socketio.SocketId{}
data = append(data, osocket.Id()) for _, user := range usersInRoom {
newRoomUsers = append(newRoomUsers, user.Id())
} }
fmt.Printf(" room %v has users %v\n", room, data) fmt.Printf(" room %v has users %v\n", room, newRoomUsers)
ioo.In(room).Emit( ioo.In(room).Emit(
"room-user-change", "room-user-change",
data, newRoomUsers,
) )
}) })
@@ -91,20 +123,22 @@ func main() {
}) })
socket.On("disconnecting", func(datas ...any) { socket.On("disconnecting", func(datas ...any) {
for _, oroom := range socket.Rooms().Keys() { for _, currentRoom := range socket.Rooms().Keys() {
ioo.In(oroom).FetchSockets()(func(sockets []*socketio.RemoteSocket, _ error) { ioo.In(currentRoom).FetchSockets()(func(usersInRoom []*socketio.RemoteSocket, _ error) {
otherClients := []socketio.SocketId{} allUsers := []socketio.SocketId{}
fmt.Printf("disconnecting %v from room %v", me, oroom) remainingUsers := []socketio.SocketId{}
for _, osocket := range sockets { fmt.Printf("disconnecting %v from room %v\n", me, currentRoom)
if osocket.Id() != me { for _, userInRoom := range usersInRoom {
otherClients = append(otherClients, osocket.Id()) allUsers = append(allUsers, userInRoom.Id())
fmt.Println("other", osocket.Id()) if userInRoom.Id() != me {
remainingUsers = append(remainingUsers, userInRoom.Id())
} }
} }
if len(otherClients) > 0 { if len(remainingUsers) > 0 {
ioo.In(oroom).Emit( fmt.Printf("leaving user, room %v has users %v -> %v\n", currentRoom, allUsers, remainingUsers)
ioo.In(currentRoom).Emit(
"room-user-change", "room-user-change",
otherClients, remainingUsers,
) )
} }
@@ -133,11 +167,7 @@ func main() {
documentStore := memory.NewDocumentStore() documentStore := memory.NewDocumentStore()
r.Get("/", func(w http.ResponseWriter, r *http.Request) { r.Mount("/", FrontEndHandler{})
w.Write([]byte("you are all set"))
fmt.Println(ulid.Make())
render.Status(r, http.StatusOK)
})
r.Route("/api/v2", func(r chi.Router) { r.Route("/api/v2", func(r chi.Router) {
r.Post("/post/", func(w http.ResponseWriter, r *http.Request) { r.Post("/post/", func(w http.ResponseWriter, r *http.Request) {
@@ -171,6 +201,7 @@ func main() {
r.Handle("/socket.io/", ioo.ServeHandler(nil)) r.Handle("/socket.io/", ioo.ServeHandler(nil))
go http.ListenAndServe(":3002", r) go http.ListenAndServe(":3002", r)
fmt.Println("listen on 3002")
exit := make(chan struct{}) exit := make(chan struct{})
SignalC := make(chan os.Signal) SignalC := make(chan os.Signal)