package internals import ( "encoding/json" "net/http" "strings" "sync" ) // credsMu guards concurrent reads/writes of appCreds.Workspaces from HTTP // handlers. Credential fields (Username/Hash/etc.) are only written at // startup via CLI flags that call os.Exit, so they don't need the lock. var credsMu sync.Mutex // WorkspacePaneLayout is a recursive pane tree. // Leaf nodes own a PTY session; split nodes contain two children. type WorkspacePaneLayout struct { Type string `json:"type"` // "leaf" | "split" ID string `json:"id,omitempty"` // PTY session hex ID (leaf only) Dir string `json:"dir,omitempty"` // "h" | "v" (split only) Ratio float64 `json:"ratio,omitempty"` // 0.1–0.9 (split only) A *WorkspacePaneLayout `json:"a,omitempty"` B *WorkspacePaneLayout `json:"b,omitempty"` } // WorkspaceTabLayout stores one tab's label and pane tree. type WorkspaceTabLayout struct { TabID string `json:"tab_id"` Label string `json:"label"` Root WorkspacePaneLayout `json:"root"` } // WorkspaceLayout is the full layout stored per workspace ID in gws-creds.json. type WorkspaceLayout struct { ID string `json:"id"` Tabs []WorkspaceTabLayout `json:"tabs"` } // handleWorkspaceAPI dispatches GET / PUT / DELETE for /api/workspace/. // All methods require auth. func handleWorkspaceAPI(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if !isAuthed(r) { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(`{"error":"unauthorized"}`)) return } id := strings.TrimPrefix(r.URL.Path, "/api/workspace/") if !validID(id) { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"invalid id"}`)) return } switch r.Method { case http.MethodGet: workspaceGet(w, id) case http.MethodPut: workspacePut(w, r, id) case http.MethodDelete: workspaceDelete(w, id) default: w.WriteHeader(http.StatusMethodNotAllowed) w.Write([]byte(`{"error":"method not allowed"}`)) } } func workspaceGet(w http.ResponseWriter, id string) { credsMu.Lock() ws := appCreds.Workspaces[id] credsMu.Unlock() if ws == nil { w.WriteHeader(http.StatusNotFound) w.Write([]byte(`{"error":"not found"}`)) return } json.NewEncoder(w).Encode(ws) //nolint:errcheck } func workspacePut(w http.ResponseWriter, r *http.Request, id string) { var ws WorkspaceLayout if err := json.NewDecoder(r.Body).Decode(&ws); err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"bad json"}`)) return } ws.ID = id credsMu.Lock() if appCreds.Workspaces == nil { appCreds.Workspaces = make(map[string]*WorkspaceLayout) } appCreds.Workspaces[id] = &ws err := saveCreds(appCreds) credsMu.Unlock() if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(`{"error":"save failed"}`)) return } w.Write([]byte(`{"ok":true}`)) } func workspaceDelete(w http.ResponseWriter, id string) { credsMu.Lock() if appCreds.Workspaces != nil { delete(appCreds.Workspaces, id) } err := saveCreds(appCreds) credsMu.Unlock() if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(`{"error":"save failed"}`)) return } w.Write([]byte(`{"ok":true}`)) }