main.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. package main
  2. import (
  3. "bytes"
  4. "embed"
  5. _ "embed"
  6. "excalidraw-complete/core"
  7. "excalidraw-complete/documents/memory"
  8. "fmt"
  9. "io"
  10. "io/fs"
  11. "net/http"
  12. "os"
  13. "os/signal"
  14. "path"
  15. "strings"
  16. "syscall"
  17. "github.com/go-chi/chi/v5"
  18. "github.com/go-chi/chi/v5/middleware"
  19. "github.com/go-chi/cors"
  20. "github.com/go-chi/render"
  21. "github.com/zishang520/engine.io/v2/types"
  22. socketio "github.com/zishang520/socket.io/v2/socket"
  23. )
  24. type (
  25. DocumentCreateResponse struct {
  26. ID string `json:"id"`
  27. }
  28. UserToFollow struct {
  29. SocketId string
  30. Username string
  31. }
  32. OnUserFollowedPayload struct {
  33. UserToFollow UserToFollow
  34. action string
  35. }
  36. )
  37. //go:embed all:frontend
  38. var assets embed.FS
  39. func Assets() (fs.FS, error) {
  40. return fs.Sub(assets, "frontend")
  41. }
  42. type FrontEndHandler struct{}
  43. func (h FrontEndHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  44. frontendHandler(w, r)
  45. }
  46. func frontendHandler(w http.ResponseWriter, r *http.Request) {
  47. upath := r.URL.Path
  48. if !strings.HasPrefix(upath, "/") {
  49. upath = "/" + upath
  50. r.URL.Path = upath
  51. }
  52. upath = path.Clean(upath)
  53. sub, err := fs.Sub(assets, "frontend")
  54. if err != nil {
  55. panic(err)
  56. }
  57. http.FileServer(http.FS(sub)).ServeHTTP(w, r)
  58. }
  59. func main() {
  60. opts := socketio.DefaultServerOptions()
  61. opts.SetMaxHttpBufferSize(5000000)
  62. opts.SetPath("/socket.io")
  63. opts.SetAllowEIO3(true)
  64. opts.SetCors(&types.Cors{
  65. Origin: "*",
  66. Credentials: true,
  67. })
  68. ioo := socketio.NewServer(nil, opts)
  69. ioo.On("connection", func(clients ...any) {
  70. socket := clients[0].(*socketio.Socket)
  71. ioo.To(socketio.Room(socket.Id())).Emit("init-room")
  72. me := socket.Id()
  73. socket.On("join-room", func(datas ...any) {
  74. room := socketio.Room(datas[0].(string))
  75. fmt.Printf("Socket %v has joined %v\n", me, room)
  76. socket.Join(room)
  77. ioo.In(room).FetchSockets()(func(usersInRoom []*socketio.RemoteSocket, _ error) {
  78. if len(usersInRoom) <= 1 {
  79. ioo.To(socketio.Room(me)).Emit("first-in-room")
  80. } else {
  81. fmt.Printf("emit new user %v in room %v\n", me, room)
  82. socket.Broadcast().To(room).Emit("new-user", me)
  83. }
  84. // Inform all clients by new users.
  85. newRoomUsers := []socketio.SocketId{}
  86. for _, user := range usersInRoom {
  87. newRoomUsers = append(newRoomUsers, user.Id())
  88. }
  89. fmt.Printf(" room %v has users %v\n", room, newRoomUsers)
  90. ioo.In(room).Emit(
  91. "room-user-change",
  92. newRoomUsers,
  93. )
  94. })
  95. })
  96. socket.On("server-broadcast", func(datas ...any) {
  97. roomID := datas[0].(string)
  98. fmt.Printf(" user %v sends update to room %v\n", me, roomID)
  99. socket.Broadcast().To(socketio.Room(roomID)).Emit("client-broadcast", datas[1], datas[2])
  100. })
  101. socket.On("server-volatile-broadcast", func(datas ...any) {
  102. roomID := datas[0].(string)
  103. fmt.Printf(" user %v sends volatile update to room %v\n", me, roomID)
  104. socket.Volatile().Broadcast().To(socketio.Room(roomID)).Emit("client-broadcast", datas[1], datas[2])
  105. })
  106. socket.On("user-follow", func(datas ...any) {
  107. // TODO()
  108. })
  109. socket.On("disconnecting", func(datas ...any) {
  110. for _, currentRoom := range socket.Rooms().Keys() {
  111. ioo.In(currentRoom).FetchSockets()(func(usersInRoom []*socketio.RemoteSocket, _ error) {
  112. allUsers := []socketio.SocketId{}
  113. remainingUsers := []socketio.SocketId{}
  114. fmt.Printf("disconnecting %v from room %v\n", me, currentRoom)
  115. for _, userInRoom := range usersInRoom {
  116. allUsers = append(allUsers, userInRoom.Id())
  117. if userInRoom.Id() != me {
  118. remainingUsers = append(remainingUsers, userInRoom.Id())
  119. }
  120. }
  121. if len(remainingUsers) > 0 {
  122. fmt.Printf("leaving user, room %v has users %v -> %v\n", currentRoom, allUsers, remainingUsers)
  123. ioo.In(currentRoom).Emit(
  124. "room-user-change",
  125. remainingUsers,
  126. )
  127. }
  128. })
  129. }
  130. })
  131. socket.On("disconnect", func(datas ...any) {
  132. socket.RemoveAllListeners("")
  133. socket.Disconnect(true)
  134. })
  135. })
  136. r := chi.NewRouter()
  137. r.Use(middleware.Logger)
  138. r.Use(cors.Handler(cors.Options{
  139. AllowedOrigins: []string{"https://*", "http://*"},
  140. AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
  141. AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "Content-Length", "X-CSRF-Token", "Token", "session", "Origin", "Host", "Connection", "Accept-Encoding", "Accept-Language", "X-Requested-With"},
  142. AllowCredentials: true,
  143. MaxAge: 300, // Maximum value not ignored by any of major browsers
  144. }))
  145. documentStore := memory.NewDocumentStore()
  146. r.Mount("/", FrontEndHandler{})
  147. r.Route("/api/v2", func(r chi.Router) {
  148. r.Post("/post/", func(w http.ResponseWriter, r *http.Request) {
  149. data := new(bytes.Buffer)
  150. _, err := io.Copy(data, r.Body)
  151. if err != nil {
  152. http.Error(w, "Failed to copy", http.StatusInternalServerError)
  153. return
  154. }
  155. id, err := documentStore.Create(r.Context(), &core.Document{Data: *data})
  156. if err != nil {
  157. http.Error(w, "Failed to save", http.StatusInternalServerError)
  158. return
  159. }
  160. render.JSON(w, r, DocumentCreateResponse{ID: id})
  161. render.Status(r, http.StatusOK)
  162. })
  163. r.Route("/{id}", func(r chi.Router) {
  164. r.Get("/", func(w http.ResponseWriter, r *http.Request) {
  165. id := chi.URLParam(r, "id")
  166. document, err := documentStore.FindID(r.Context(), id)
  167. if err != nil {
  168. http.Error(w, "not found", http.StatusNotFound)
  169. return
  170. }
  171. w.Write(document.Data.Bytes())
  172. })
  173. })
  174. })
  175. r.Handle("/socket.io/", ioo.ServeHandler(nil))
  176. go http.ListenAndServe(":3002", r)
  177. fmt.Println("listen on 3002")
  178. exit := make(chan struct{})
  179. SignalC := make(chan os.Signal)
  180. signal.Notify(SignalC, os.Interrupt, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
  181. go func() {
  182. for s := range SignalC {
  183. switch s {
  184. case os.Interrupt, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT:
  185. close(exit)
  186. return
  187. }
  188. }
  189. }()
  190. <-exit
  191. ioo.Close(nil)
  192. os.Exit(0)
  193. }