✨ Implement WebSocket API
This commit is contained in:
130
api.go
130
api.go
@ -1,12 +1,22 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{}
|
||||||
|
|
||||||
|
type WsClient struct {
|
||||||
|
SendUpdates bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var wsClients = make(map[*websocket.Conn]WsClient)
|
||||||
|
|
||||||
func initApi() {
|
func initApi() {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
@ -26,6 +36,8 @@ func initApi() {
|
|||||||
router.PUT("/api/routes", handleSetRoute)
|
router.PUT("/api/routes", handleSetRoute)
|
||||||
router.DELETE("/api/routes", handleDeleteRoute)
|
router.DELETE("/api/routes", handleDeleteRoute)
|
||||||
|
|
||||||
|
router.GET("/ws", handleWs)
|
||||||
|
|
||||||
router.Run(fmt.Sprintf("%s:%d", config.Web.Bind, config.Web.Port))
|
router.Run(fmt.Sprintf("%s:%d", config.Web.Bind, config.Web.Port))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,6 +120,7 @@ func createPort(body CreatePortRequest) (gin.H, gin.H) {
|
|||||||
portConfig.Output = append(portConfig.Output, newPort)
|
portConfig.Output = append(portConfig.Output, newPort)
|
||||||
}
|
}
|
||||||
saveConfig("ports", &portConfig)
|
saveConfig("ports", &portConfig)
|
||||||
|
onPortListChange()
|
||||||
return gin.H{"success": true, "UUID": id}, nil
|
return gin.H{"success": true, "UUID": id}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,6 +152,7 @@ func deletePort(id string) gin.H {
|
|||||||
unregisterPort(port)
|
unregisterPort(port)
|
||||||
|
|
||||||
saveConfig("ports", &portConfig)
|
saveConfig("ports", &portConfig)
|
||||||
|
onPortListChange()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,6 +213,7 @@ func setState(id string, body SetPortRequest) (gin.H, gin.H) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveConfig("ports", &portConfig)
|
saveConfig("ports", &portConfig)
|
||||||
|
onPortChange(port)
|
||||||
return gin.H{"success": true, "state": port.State}, nil
|
return gin.H{"success": true, "state": port.State}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,6 +251,7 @@ func createRoute(id string, body CreateRouteRequest) gin.H {
|
|||||||
|
|
||||||
from.Route = append(from.Route, PortRoute{ToUUID: body.To, Mute: true, Volume: 0, Balance: 0})
|
from.Route = append(from.Route, PortRoute{ToUUID: body.To, Mute: true, Volume: 0, Balance: 0})
|
||||||
saveConfig("ports", &portConfig)
|
saveConfig("ports", &portConfig)
|
||||||
|
onPortChange(from)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,6 +301,7 @@ func setRoute(fromId string, toId string, body SetRouteRequest) (gin.H, gin.H) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveConfig("ports", &portConfig)
|
saveConfig("ports", &portConfig)
|
||||||
|
onPortChange(from)
|
||||||
return gin.H{"success": true, "state": from.Route[i]}, nil
|
return gin.H{"success": true, "state": from.Route[i]}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,8 +332,121 @@ func deleteRoute(fromId string, toId string) gin.H {
|
|||||||
|
|
||||||
from.Route = append(from.Route[:i], from.Route[i+1:]...)
|
from.Route = append(from.Route[:i], from.Route[i+1:]...)
|
||||||
saveConfig("ports", &portConfig)
|
saveConfig("ports", &portConfig)
|
||||||
|
onPortListChange()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return gin.H{"success": false, "error": "No such route exists!"}
|
return gin.H{"success": false, "error": "No such route exists!"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WebsocketMessage struct {
|
||||||
|
RequestId string `json:"requestId"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
UUID string `json:"UUID"`
|
||||||
|
ToUUID string `json:"toUUID"`
|
||||||
|
PortData CreatePortRequest `json:"portData"`
|
||||||
|
StateData SetPortRequest `json:"stateData"`
|
||||||
|
RouteData CreateRouteRequest `json:"routeData"`
|
||||||
|
RouteStateData SetRouteRequest `json:"routeStateData"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebsocketResponse struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
RequestId string `json:"requestId,omitempty"`
|
||||||
|
RequestMethod string `json:"requestMethod,omitempty"`
|
||||||
|
ResponseData interface{} `json:"responseData"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func wsSendRes(conn *websocket.Conn, res gin.H, err gin.H, request *WebsocketMessage) {
|
||||||
|
if err != nil {
|
||||||
|
data, _ := json.Marshal(WebsocketResponse{
|
||||||
|
Type: "error",
|
||||||
|
RequestId: request.RequestId,
|
||||||
|
RequestMethod: request.Method,
|
||||||
|
ResponseData: err,
|
||||||
|
})
|
||||||
|
conn.WriteMessage(1, data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := WebsocketResponse{
|
||||||
|
Type: "response",
|
||||||
|
RequestId: request.RequestId,
|
||||||
|
RequestMethod: request.Method,
|
||||||
|
ResponseData: gin.H{"success": true},
|
||||||
|
}
|
||||||
|
if res != nil {
|
||||||
|
response.ResponseData = res
|
||||||
|
}
|
||||||
|
|
||||||
|
data, _ := json.Marshal(response)
|
||||||
|
conn.WriteMessage(1, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleWs(c *gin.Context) {
|
||||||
|
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("WARN! Handling websocket connection failed!")
|
||||||
|
c.JSON(500, gin.H{"success": false, "error": "Handling websocket connection failed!"})
|
||||||
|
}
|
||||||
|
|
||||||
|
wsClients[conn] = WsClient{SendUpdates: false}
|
||||||
|
|
||||||
|
for {
|
||||||
|
var content WebsocketMessage
|
||||||
|
jsonErr := conn.ReadJSON(&content)
|
||||||
|
if jsonErr != nil {
|
||||||
|
delete(wsClients, conn)
|
||||||
|
conn.Close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var res, err gin.H = nil, nil
|
||||||
|
switch content.Method {
|
||||||
|
case "listPorts":
|
||||||
|
res = gin.H{"inputs": portConfig.Input, "outputs": portConfig.Output}
|
||||||
|
case "createPort":
|
||||||
|
res, err = createPort(content.PortData)
|
||||||
|
case "setPortState":
|
||||||
|
res, err = setState(content.UUID, content.StateData)
|
||||||
|
case "deletePort":
|
||||||
|
err = deletePort(content.UUID)
|
||||||
|
case "createRoute":
|
||||||
|
err = createRoute(content.UUID, content.RouteData)
|
||||||
|
case "setRouteState":
|
||||||
|
res, err = setRoute(content.UUID, content.ToUUID, content.RouteStateData)
|
||||||
|
case "deleteRoute":
|
||||||
|
err = deleteRoute(content.UUID, content.ToUUID)
|
||||||
|
case "enableUpdates":
|
||||||
|
if clientOpts, ok := wsClients[conn]; ok {
|
||||||
|
clientOpts.SendUpdates = true
|
||||||
|
wsClients[conn] = clientOpts
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = gin.H{"success": false, "error": "Method is not implemented"}
|
||||||
|
}
|
||||||
|
wsSendRes(conn, res, err, &content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func onPortChange(port *Port) {
|
||||||
|
for conn := range wsClients {
|
||||||
|
if !wsClients[conn].SendUpdates {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
jsonData, _ := json.Marshal(WebsocketResponse{Type: "portChange", ResponseData: port})
|
||||||
|
conn.WriteMessage(1, jsonData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func onPortListChange() {
|
||||||
|
for conn := range wsClients {
|
||||||
|
if !wsClients[conn].SendUpdates {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
jsonData, _ := json.Marshal(WebsocketResponse{
|
||||||
|
Type: "postListChange",
|
||||||
|
ResponseData: gin.H{"inputs": portConfig.Input, "outputs": portConfig.Output},
|
||||||
|
})
|
||||||
|
conn.WriteMessage(1, jsonData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
9
go.mod
9
go.mod
@ -5,6 +5,7 @@ go 1.21
|
|||||||
require (
|
require (
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/gorilla/websocket v1.5.1
|
||||||
github.com/pelletier/go-toml/v2 v2.1.1
|
github.com/pelletier/go-toml/v2 v2.1.1
|
||||||
github.com/xthexder/go-jack v0.0.0-20220805234212-bc8604043aba
|
github.com/xthexder/go-jack v0.0.0-20220805234212-bc8604043aba
|
||||||
)
|
)
|
||||||
@ -27,10 +28,10 @@ require (
|
|||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
golang.org/x/crypto v0.9.0 // indirect
|
golang.org/x/crypto v0.14.0 // indirect
|
||||||
golang.org/x/net v0.10.0 // indirect
|
golang.org/x/net v0.17.0 // indirect
|
||||||
golang.org/x/sys v0.8.0 // indirect
|
golang.org/x/sys v0.13.0 // indirect
|
||||||
golang.org/x/text v0.9.0 // indirect
|
golang.org/x/text v0.13.0 // indirect
|
||||||
google.golang.org/protobuf v1.30.0 // indirect
|
google.golang.org/protobuf v1.30.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
18
go.sum
18
go.sum
@ -29,6 +29,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||||
|
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
@ -67,16 +69,16 @@ github.com/xthexder/go-jack v0.0.0-20220805234212-bc8604043aba/go.mod h1:T6DswVP
|
|||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
Reference in New Issue
Block a user