main.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. package main
  2. import (
  3. "embed"
  4. _ "embed"
  5. "excalidraw-complete/core"
  6. documents "excalidraw-complete/handlers/api"
  7. "excalidraw-complete/stores"
  8. "fmt"
  9. "io/fs"
  10. "net/http"
  11. "os"
  12. "os/signal"
  13. "syscall"
  14. "github.com/go-chi/chi/v5"
  15. "github.com/go-chi/chi/v5/middleware"
  16. "github.com/go-chi/cors"
  17. "github.com/zishang520/engine.io/v2/types"
  18. socketio "github.com/zishang520/socket.io/v2/socket"
  19. )
  20. type (
  21. UserToFollow struct {
  22. SocketId string
  23. Username string
  24. }
  25. OnUserFollowedPayload struct {
  26. UserToFollow UserToFollow
  27. action string
  28. }
  29. )
  30. //go:embed all:frontend
  31. var assets embed.FS
  32. func handleUI() http.Handler {
  33. sub, err := fs.Sub(assets, "frontend")
  34. if err != nil {
  35. panic(err)
  36. }
  37. return http.FileServer(http.FS(sub))
  38. }
  39. func setupRouter(documentStore core.DocumentStore) *chi.Mux {
  40. r := chi.NewRouter()
  41. r.Use(middleware.Logger)
  42. r.Use(cors.Handler(cors.Options{
  43. AllowedOrigins: []string{"https://*", "http://*"},
  44. AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
  45. AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "Content-Length", "X-CSRF-Token", "Token", "session", "Origin", "Host", "Connection", "Accept-Encoding", "Accept-Language", "X-Requested-With"},
  46. AllowCredentials: true,
  47. MaxAge: 300, // Maximum value not ignored by any of major browsers
  48. }))
  49. r.Route("/api/v2", func(r chi.Router) {
  50. r.Post("/post/", documents.HandleCreate(documentStore))
  51. r.Route("/{id}", func(r chi.Router) {
  52. r.Get("/", documents.HandleGet(documentStore))
  53. })
  54. })
  55. return r
  56. }
  57. func setupSocketIO() *socketio.Server {
  58. opts := socketio.DefaultServerOptions()
  59. opts.SetMaxHttpBufferSize(5000000)
  60. opts.SetPath("/socket.io")
  61. opts.SetAllowEIO3(true)
  62. opts.SetCors(&types.Cors{
  63. Origin: "*",
  64. Credentials: true,
  65. })
  66. ioo := socketio.NewServer(nil, opts)
  67. ioo.On("connection", func(clients ...any) {
  68. socket := clients[0].(*socketio.Socket)
  69. ioo.To(socketio.Room(socket.Id())).Emit("init-room")
  70. me := socket.Id()
  71. socket.On("join-room", func(datas ...any) {
  72. room := socketio.Room(datas[0].(string))
  73. fmt.Printf("Socket %v has joined %v\n", me, room)
  74. socket.Join(room)
  75. ioo.In(room).FetchSockets()(func(usersInRoom []*socketio.RemoteSocket, _ error) {
  76. if len(usersInRoom) <= 1 {
  77. ioo.To(socketio.Room(me)).Emit("first-in-room")
  78. } else {
  79. fmt.Printf("emit new user %v in room %v\n", me, room)
  80. socket.Broadcast().To(room).Emit("new-user", me)
  81. }
  82. // Inform all clients by new users.
  83. newRoomUsers := []socketio.SocketId{}
  84. for _, user := range usersInRoom {
  85. newRoomUsers = append(newRoomUsers, user.Id())
  86. }
  87. fmt.Printf(" room %v has users %v\n", room, newRoomUsers)
  88. ioo.In(room).Emit(
  89. "room-user-change",
  90. newRoomUsers,
  91. )
  92. })
  93. })
  94. socket.On("server-broadcast", func(datas ...any) {
  95. roomID := datas[0].(string)
  96. fmt.Printf(" user %v sends update to room %v\n", me, roomID)
  97. socket.Broadcast().To(socketio.Room(roomID)).Emit("client-broadcast", datas[1], datas[2])
  98. })
  99. socket.On("server-volatile-broadcast", func(datas ...any) {
  100. roomID := datas[0].(string)
  101. fmt.Printf(" user %v sends volatile update to room %v\n", me, roomID)
  102. socket.Volatile().Broadcast().To(socketio.Room(roomID)).Emit("client-broadcast", datas[1], datas[2])
  103. })
  104. socket.On("user-follow", func(datas ...any) {
  105. // TODO()
  106. })
  107. socket.On("disconnecting", func(datas ...any) {
  108. for _, currentRoom := range socket.Rooms().Keys() {
  109. ioo.In(currentRoom).FetchSockets()(func(usersInRoom []*socketio.RemoteSocket, _ error) {
  110. allUsers := []socketio.SocketId{}
  111. remainingUsers := []socketio.SocketId{}
  112. fmt.Printf("disconnecting %v from room %v\n", me, currentRoom)
  113. for _, userInRoom := range usersInRoom {
  114. allUsers = append(allUsers, userInRoom.Id())
  115. if userInRoom.Id() != me {
  116. remainingUsers = append(remainingUsers, userInRoom.Id())
  117. }
  118. }
  119. if len(remainingUsers) > 0 {
  120. fmt.Printf("leaving user, room %v has users %v -> %v\n", currentRoom, allUsers, remainingUsers)
  121. ioo.In(currentRoom).Emit(
  122. "room-user-change",
  123. remainingUsers,
  124. )
  125. }
  126. })
  127. }
  128. })
  129. socket.On("disconnect", func(datas ...any) {
  130. socket.RemoveAllListeners("")
  131. socket.Disconnect(true)
  132. })
  133. })
  134. return ioo
  135. }
  136. func waitForShutdown(ioo *socketio.Server) {
  137. exit := make(chan struct{})
  138. SignalC := make(chan os.Signal)
  139. signal.Notify(SignalC, os.Interrupt, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
  140. go func() {
  141. for s := range SignalC {
  142. switch s {
  143. case os.Interrupt, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT:
  144. close(exit)
  145. return
  146. }
  147. }
  148. }()
  149. <-exit
  150. ioo.Close(nil)
  151. os.Exit(0)
  152. fmt.Println("Shutting down...")
  153. // TODO(patwie): Close other resources
  154. os.Exit(0)
  155. }
  156. func main() {
  157. documentStore := stores.GetStore() // Make sure this is well-defined in your "stores" package
  158. r := setupRouter(documentStore)
  159. ioo := setupSocketIO()
  160. r.Handle("/socket.io/", ioo.ServeHandler(nil))
  161. r.Mount("/", handleUI())
  162. go http.ListenAndServe(":3002", r)
  163. fmt.Println("listen on 3002")
  164. waitForShutdown(ioo)
  165. }