Intercept calls to firebase API

Instead of calling any third-party or home, we provide the stub for
firebase API calls.
This commit is contained in:
patwie
2024-04-14 13:29:39 +00:00
parent 3893dfb13e
commit eea09906f4
3 changed files with 210 additions and 17 deletions
+131
View File
@@ -0,0 +1,131 @@
package firebase
import (
"fmt"
"net/http"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
)
type (
BatchGetRequest struct {
Documents []string `json:"documents"`
}
BatchGetEmptyResponse struct {
Missing string `json:"missing"`
ReadTime string `json:"readTime"`
}
FoundInfoResponse struct {
Name string `json:"name"`
Fields interface{} `json:"fields"`
CreateTime string `json:"createTime"`
UpdateTime string `json:"updateTime"`
}
BatchGetExistsResponse struct {
Found FoundInfoResponse `json:"found"`
ReadTime string `json:"readTime"`
}
UpdateRequest struct {
Name string `json:"name"`
Fields interface{} `json:"fields"`
}
WriteRequest struct {
Update UpdateRequest `json:"update"`
}
BatchCommitRequest struct {
Writes []WriteRequest `json:"writes"`
}
WriteResult struct {
UpdateTime string `json:"updateTime"`
}
BatchCommitResponse struct {
WriteResults []WriteResult `json:"writeResults"`
CommitTime string `json:"commitTime"`
}
)
var savedItems = make(map[string]interface{})
func (body *BatchGetRequest) Bind(r *http.Request) (err error) {
return nil
}
func (body *BatchCommitRequest) Bind(r *http.Request) (err error) {
return nil
}
func HandleBatchCommit() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
projectId := chi.URLParam(r, "project_id")
databaseId := chi.URLParam(r, "database_id")
_ = projectId
_ = databaseId
data := &BatchCommitRequest{}
// Seems like requests is text/plain but content is json ...
if err := render.DecodeJSON(r.Body, data); err != nil {
fmt.Println(err)
render.Status(r, http.StatusBadRequest)
return
}
savedItems[data.Writes[0].Update.Name] = data.Writes[0].Update.Fields
render.JSON(w, r, BatchCommitResponse{
CommitTime: time.Now().Format(time.RFC3339),
WriteResults: []WriteResult{
WriteResult{UpdateTime: time.Now().Format(time.RFC3339)},
},
})
render.Status(r, http.StatusOK)
return
}
}
func HandleBatchGet() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
projectId := chi.URLParam(r, "project_id")
databaseId := chi.URLParam(r, "database_id")
fmt.Printf("Got %v and %v\n", projectId, databaseId)
data := &BatchGetRequest{}
// Seems like requests is text/plain but content is json ...
if err := render.DecodeJSON(r.Body, data); err != nil {
fmt.Println(err)
render.Status(r, http.StatusBadRequest)
return
}
key := data.Documents[0]
fmt.Printf("Got key %v \n", key)
fields, ok := savedItems[key]
if !ok {
fmt.Println("missing key")
render.JSON(w, r, []BatchGetEmptyResponse{BatchGetEmptyResponse{
Missing: key,
ReadTime: time.Now().Format(time.RFC3339),
}})
render.Status(r, http.StatusOK)
return
}
fmt.Println("existing key")
render.JSON(w, r, []BatchGetExistsResponse{BatchGetExistsResponse{
Found: FoundInfoResponse{
Name: key,
Fields: fields,
CreateTime: time.Now().Format(time.RFC3339),
UpdateTime: time.Now().Format(time.RFC3339),
},
ReadTime: time.Now().Format(time.RFC3339),
}})
render.Status(r, http.StatusOK)
return
}
}
+79 -17
View File
@@ -4,19 +4,23 @@ import (
"embed" "embed"
_ "embed" _ "embed"
"excalidraw-complete/core" "excalidraw-complete/core"
documents "excalidraw-complete/handlers/api" "excalidraw-complete/handlers/api/documents"
"excalidraw-complete/handlers/api/firebase"
"excalidraw-complete/stores" "excalidraw-complete/stores"
"fmt" "fmt"
"io"
"io/fs" "io/fs"
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
"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/zishang520/engine.io/v2/types" "github.com/zishang520/engine.io/v2/types"
"github.com/zishang520/engine.io/v2/utils"
socketio "github.com/zishang520/socket.io/v2/socket" socketio "github.com/zishang520/socket.io/v2/socket"
) )
@@ -40,7 +44,58 @@ func handleUI() http.Handler {
if err != nil { if err != nil {
panic(err) panic(err)
} }
return http.FileServer(http.FS(sub)) // Let's hot-patch all calls to firebase DB
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
originalPath := r.URL.Path
originalPath = strings.TrimPrefix(originalPath, "/")
fmt.Println(originalPath)
file, err := sub.Open(originalPath)
if err != nil {
http.Error(w, "File not found", http.StatusNotFound)
return
}
defer file.Close()
fileContent, err := io.ReadAll(file)
if err != nil {
http.Error(w, "Error reading file", http.StatusInternalServerError)
return
}
modifiedContent := strings.ReplaceAll(string(fileContent), "firestore.googleapis.com", "localhost:3002")
modifiedContent = strings.ReplaceAll(modifiedContent, "ssl=!0", "ssl=0")
modifiedContent = strings.ReplaceAll(modifiedContent, "ssl:!0", "ssl:0")
if modifiedContent != string(fileContent) {
fmt.Println("has replace")
}
// Set the correct Content-Type based on the file extension
contentType := http.DetectContentType([]byte(modifiedContent))
switch {
case strings.HasSuffix(originalPath, ".js"):
contentType = "application/javascript"
case strings.HasSuffix(originalPath, ".html"):
contentType = "text/html"
case strings.HasSuffix(originalPath, ".css"):
contentType = "text/css"
case strings.HasSuffix(originalPath, ".wasm"):
contentType = "application/wasm"
case strings.HasSuffix(originalPath, ".tsx"):
contentType = "text/typescript"
case strings.HasSuffix(originalPath, ".png"):
contentType = "image/png"
case strings.HasSuffix(originalPath, ".woff2"):
contentType = "font/woff2"
}
// Serve the modified content
w.Header().Set("Content-Type", contentType)
_, err = w.Write([]byte(modifiedContent))
if err != nil {
http.Error(w, "Error serving file", http.StatusInternalServerError)
return
}
return
})
} }
func setupRouter(documentStore core.DocumentStore) *chi.Mux { func setupRouter(documentStore core.DocumentStore) *chi.Mux {
@@ -55,6 +110,11 @@ func setupRouter(documentStore core.DocumentStore) *chi.Mux {
MaxAge: 300, // Maximum value not ignored by any of major browsers MaxAge: 300, // Maximum value not ignored by any of major browsers
})) }))
r.Route("/v1/projects/{project_id}/databases/{database_id}", func(r chi.Router) {
r.Post("/documents:commit", firebase.HandleBatchCommit())
r.Post("/documents:batchGet", firebase.HandleBatchGet())
})
r.Route("/api/v2", func(r chi.Router) { r.Route("/api/v2", func(r chi.Router) {
r.Post("/post/", documents.HandleCreate(documentStore)) r.Post("/post/", documents.HandleCreate(documentStore))
r.Route("/{id}", func(r chi.Router) { r.Route("/{id}", func(r chi.Router) {
@@ -72,21 +132,24 @@ func setupSocketIO() *socketio.Server {
Origin: "*", Origin: "*",
Credentials: true, Credentials: true,
}) })
opts.SetTransports(types.NewSet("polling", "webtransport"))
ioo := socketio.NewServer(nil, opts) ioo := socketio.NewServer(nil, opts)
ioo.On("connection", func(clients ...any) { ioo.On("connection", func(clients ...any) {
socket := clients[0].(*socketio.Socket) socket := clients[0].(*socketio.Socket)
ioo.To(socketio.Room(socket.Id())).Emit("init-room")
me := socket.Id() me := socket.Id()
myRoom := socketio.Room(me)
ioo.To(myRoom).Emit("init-room")
utils.Log().Println("init room ", myRoom)
socket.On("join-room", func(datas ...any) { socket.On("join-room", func(datas ...any) {
room := socketio.Room(datas[0].(string)) room := socketio.Room(datas[0].(string))
fmt.Printf("Socket %v has joined %v\n", me, room) utils.Log().Printf("Socket %v has joined %v\n", me, room)
socket.Join(room) socket.Join(room)
ioo.In(room).FetchSockets()(func(usersInRoom []*socketio.RemoteSocket, _ error) { ioo.In(room).FetchSockets()(func(usersInRoom []*socketio.RemoteSocket, _ error) {
if len(usersInRoom) <= 1 { if len(usersInRoom) <= 1 {
ioo.To(socketio.Room(me)).Emit("first-in-room") ioo.To(myRoom).Emit("first-in-room")
} else { } else {
fmt.Printf("emit new user %v in room %v\n", me, room) utils.Log().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)
} }
@@ -95,7 +158,7 @@ func setupSocketIO() *socketio.Server {
for _, user := range usersInRoom { for _, user := range usersInRoom {
newRoomUsers = append(newRoomUsers, user.Id()) newRoomUsers = append(newRoomUsers, user.Id())
} }
fmt.Printf(" room %v has users %v\n", room, newRoomUsers) utils.Log().Println(" room ", room, " has users ", newRoomUsers)
ioo.In(room).Emit( ioo.In(room).Emit(
"room-user-change", "room-user-change",
newRoomUsers, newRoomUsers,
@@ -105,12 +168,12 @@ func setupSocketIO() *socketio.Server {
}) })
socket.On("server-broadcast", func(datas ...any) { socket.On("server-broadcast", func(datas ...any) {
roomID := datas[0].(string) roomID := datas[0].(string)
fmt.Printf(" user %v sends update to room %v\n", me, roomID) utils.Log().Printf(" user %v sends update to room %v\n", me, roomID)
socket.Broadcast().To(socketio.Room(roomID)).Emit("client-broadcast", datas[1], datas[2]) socket.Broadcast().To(socketio.Room(roomID)).Emit("client-broadcast", datas[1], datas[2])
}) })
socket.On("server-volatile-broadcast", func(datas ...any) { socket.On("server-volatile-broadcast", func(datas ...any) {
roomID := datas[0].(string) roomID := datas[0].(string)
fmt.Printf(" user %v sends volatile update to room %v\n", me, roomID) utils.Log().Printf(" user %v sends volatile update to room %v\n", me, roomID)
socket.Volatile().Broadcast().To(socketio.Room(roomID)).Emit("client-broadcast", datas[1], datas[2]) socket.Volatile().Broadcast().To(socketio.Room(roomID)).Emit("client-broadcast", datas[1], datas[2])
}) })
@@ -121,20 +184,18 @@ func setupSocketIO() *socketio.Server {
socket.On("disconnecting", func(datas ...any) { socket.On("disconnecting", func(datas ...any) {
for _, currentRoom := range socket.Rooms().Keys() { for _, currentRoom := range socket.Rooms().Keys() {
ioo.In(currentRoom).FetchSockets()(func(usersInRoom []*socketio.RemoteSocket, _ error) { ioo.In(currentRoom).FetchSockets()(func(usersInRoom []*socketio.RemoteSocket, _ error) {
allUsers := []socketio.SocketId{} otherClients := []socketio.SocketId{}
remainingUsers := []socketio.SocketId{} utils.Log().Printf("disconnecting %v from room %v\n", me, currentRoom)
fmt.Printf("disconnecting %v from room %v\n", me, currentRoom)
for _, userInRoom := range usersInRoom { for _, userInRoom := range usersInRoom {
allUsers = append(allUsers, userInRoom.Id())
if userInRoom.Id() != me { if userInRoom.Id() != me {
remainingUsers = append(remainingUsers, userInRoom.Id()) otherClients = append(otherClients, userInRoom.Id())
} }
} }
if len(remainingUsers) > 0 { if len(otherClients) > 0 {
fmt.Printf("leaving user, room %v has users %v -> %v\n", currentRoom, allUsers, remainingUsers) utils.Log().Printf("leaving user, room %v has users %v\n", currentRoom, otherClients)
ioo.In(currentRoom).Emit( ioo.In(currentRoom).Emit(
"room-user-change", "room-user-change",
remainingUsers, otherClients,
) )
} }
@@ -149,6 +210,7 @@ func setupSocketIO() *socketio.Server {
socket.Disconnect(true) socket.Disconnect(true)
}) })
}) })
utils.Log().Println("%v", ioo)
return ioo return ioo
} }