282 lines
6.3 KiB
Go
282 lines
6.3 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"code.ivysaur.me/libnmdc"
|
|
"github.com/googollee/go-socket.io"
|
|
)
|
|
|
|
var VERSION string = `nmdc-webfrontend/devel-unreleased`
|
|
|
|
type App struct {
|
|
cfg *Config
|
|
}
|
|
|
|
func NewApp(ConfigFilePath string) (*App, error) {
|
|
|
|
cfgData, err := ioutil.ReadFile(ConfigFilePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cfg := Config{}
|
|
err = json.Unmarshal(cfgData, &cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &App{cfg: &cfg}, nil
|
|
}
|
|
|
|
type UserMessageStruct struct {
|
|
User string `json:"user"`
|
|
Message string `json:"message,omitempty"`
|
|
}
|
|
|
|
func (this *App) HubWorker(Nick, Pass string, so socketio.Socket, done chan struct{}) {
|
|
|
|
log.Printf("[%s] Connecting to hub\n", so.Id())
|
|
|
|
selfUser := libnmdc.NewUserInfo(Nick)
|
|
selfUser.ClientTag = this.cfg.Hub.Tag
|
|
selfUser.ClientVersion = libnmdc.DEFAULT_CLIENT_VERSION
|
|
|
|
hco := libnmdc.HubConnectionOptions{
|
|
Address: libnmdc.HubAddress(fmt.Sprintf("%s:%d", this.cfg.Hub.Address, this.cfg.Hub.Port)),
|
|
Self: *selfUser,
|
|
NickPassword: Pass,
|
|
NumEventsToBuffer: 0,
|
|
}
|
|
|
|
hub := hco.Connect()
|
|
|
|
defer func() {
|
|
hub.Disconnect()
|
|
so.Emit("disconnect") // necessary? https://github.com/googollee/go-socket.io/issues/117
|
|
log.Printf("[%s] Leaving worker\n", so.Id())
|
|
}()
|
|
|
|
// Register after-connected SIO handlers
|
|
|
|
so.On("pub", func(data map[string]string) {
|
|
hub.SayPublic(data["message"])
|
|
})
|
|
|
|
so.On("priv", func(data map[string]string) {
|
|
hub.SayPrivate(data["user"], data["message"])
|
|
})
|
|
|
|
so.On("raw", func(data map[string]string) {
|
|
hub.SayRaw(data["message"])
|
|
})
|
|
|
|
// Loop hub connection
|
|
|
|
serveUserInfo := func(nick string) {
|
|
props := ""
|
|
hub.Users(func(users *map[string]libnmdc.UserInfo) error {
|
|
uinfo, ok := (*users)[nick]
|
|
if !ok {
|
|
return nil // just skip
|
|
}
|
|
|
|
bProps, err := json.Marshal(uinfo)
|
|
if err != nil {
|
|
return nil // just skip
|
|
}
|
|
|
|
props = string(bProps)
|
|
return nil
|
|
})
|
|
|
|
// 'Message' is a json-encoded param with user properties
|
|
if len(props) > 0 {
|
|
so.Emit("info", UserMessageStruct{User: nick, Message: props})
|
|
}
|
|
}
|
|
|
|
for {
|
|
select {
|
|
|
|
case hev, ok := <-hub.OnEvent:
|
|
if !ok {
|
|
log.Printf("[%s] hub chan closed\n", so.Id())
|
|
return // abandon
|
|
}
|
|
|
|
switch hev.EventType {
|
|
case libnmdc.EVENT_SYSTEM_MESSAGE_FROM_CONN, libnmdc.EVENT_SYSTEM_MESSAGE_FROM_HUB:
|
|
so.Emit("sys", hev.Message)
|
|
|
|
case libnmdc.EVENT_PUBLIC:
|
|
so.Emit("pub", UserMessageStruct{User: hev.Nick, Message: hev.Message})
|
|
|
|
case libnmdc.EVENT_PRIVATE:
|
|
so.Emit("priv", UserMessageStruct{User: hev.Nick, Message: hev.Message})
|
|
|
|
case libnmdc.EVENT_USER_UPDATED_INFO:
|
|
serveUserInfo(hev.Nick)
|
|
|
|
case libnmdc.EVENT_USER_JOINED:
|
|
so.Emit("join", UserMessageStruct{User: hev.Nick})
|
|
serveUserInfo(hev.Nick)
|
|
|
|
case libnmdc.EVENT_USER_PART:
|
|
so.Emit("part", UserMessageStruct{User: hev.Nick})
|
|
|
|
case libnmdc.EVENT_CONNECTION_STATE_CHANGED:
|
|
if hev.StateChange == libnmdc.CONNECTIONSTATE_CONNECTED {
|
|
log.Printf("[%s] Connected to hub\n", so.Id())
|
|
so.Emit("hello")
|
|
} else if hev.StateChange == libnmdc.CONNECTIONSTATE_DISCONNECTED {
|
|
so.Emit("close")
|
|
} else {
|
|
so.Emit("sys", hev.StateChange.String())
|
|
}
|
|
|
|
case libnmdc.EVENT_HUBNAME_CHANGED:
|
|
so.Emit("hubname", hev.Nick)
|
|
|
|
case libnmdc.EVENT_USERCOMMAND:
|
|
so.Emit("usercommand", map[string]interface{}{
|
|
"type": hev.UserCommand.Type,
|
|
"context": hev.UserCommand.Context,
|
|
"title": hev.UserCommand.Message,
|
|
"raw": hev.UserCommand.Command,
|
|
})
|
|
|
|
}
|
|
|
|
case <-done:
|
|
log.Printf("[%s] done chan closed\n", so.Id())
|
|
return // abandon
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func (this *App) SocketIOServer(so socketio.Socket) {
|
|
log.Printf("[%s] Client connected", so.Id())
|
|
|
|
so.Emit("cls")
|
|
so.Emit("hubname", this.cfg.Web.Title)
|
|
so.Emit("raw", this.cfg.App.MotdHTML+"<br>")
|
|
so.Emit("sys", "Enter a name to connect as (or name:pass for a registered nick)")
|
|
|
|
doneChan := make(chan struct{}, 0)
|
|
|
|
so.On("hello", func(data map[string]string) {
|
|
log.Printf("[%s] Connection request", so.Id())
|
|
go this.HubWorker(data["nick"], data["pass"], so, doneChan)
|
|
})
|
|
|
|
so.On("disconnection", func() { // n.b. not 'disconnect' (??)
|
|
log.Printf("[%s] Client dropped", so.Id())
|
|
close(doneChan)
|
|
})
|
|
|
|
}
|
|
|
|
func (this *App) customFaviconHandler(w http.ResponseWriter, r *http.Request) {
|
|
http.ServeFile(w, r, "favicon.ico")
|
|
}
|
|
|
|
func (this *App) StaticRequestHandler(w http.ResponseWriter, r *http.Request) {
|
|
fileName := r.URL.Path[1:]
|
|
if fileName == "" {
|
|
fileName = "index.htm"
|
|
}
|
|
|
|
data, err := Asset(fileName)
|
|
if err != nil {
|
|
w.WriteHeader(404)
|
|
return
|
|
}
|
|
|
|
knownContentTypes := map[string]string{
|
|
".htm": "text/html",
|
|
".png": "image/png",
|
|
".ico": "image/x-icon",
|
|
// No CSS/JS since they're embedded in the HTML
|
|
}
|
|
|
|
foundMime := false
|
|
for ext, mimeType := range knownContentTypes {
|
|
if strings.HasSuffix(fileName, ext) {
|
|
w.Header().Set("Content-Type", mimeType)
|
|
foundMime = true
|
|
break
|
|
}
|
|
}
|
|
if !foundMime {
|
|
w.Header().Set("Content-Type", "application/x-octet-stream")
|
|
}
|
|
dataInfo, _ := AssetInfo(fileName)
|
|
w.Header().Set("Content-Length", fmt.Sprintf("%d", dataInfo.Size()))
|
|
|
|
w.Write(data)
|
|
}
|
|
|
|
func (this *App) RunServer() {
|
|
|
|
// Inner mux {{
|
|
innerMux := http.NewServeMux()
|
|
|
|
// Socket.io handler
|
|
server, err := socketio.NewServer(nil)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
server.On("connection", this.SocketIOServer)
|
|
server.On("error", func(so socketio.Socket, err error) {
|
|
log.Println("error:", err)
|
|
})
|
|
innerMux.Handle("/socket.io/", server)
|
|
|
|
// Custom favicon handler
|
|
if this.cfg.Web.CustomFavicon {
|
|
innerMux.HandleFunc("/favicon.ico", this.customFaviconHandler)
|
|
}
|
|
|
|
// Asset handler
|
|
if this.cfg.Web.ExternalWebroot {
|
|
innerMux.Handle("/", http.FileServer(http.Dir("client")))
|
|
} else {
|
|
innerMux.HandleFunc("/", this.StaticRequestHandler)
|
|
}
|
|
// }}
|
|
|
|
// Wrapper mux {{
|
|
outerMux := http.NewServeMux()
|
|
outerMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Server", VERSION)
|
|
innerMux.ServeHTTP(w, r)
|
|
})
|
|
// }}
|
|
|
|
// Listen and serve
|
|
bindAddr := fmt.Sprintf("%s:%d", this.cfg.Web.BindTo, this.cfg.Web.Port)
|
|
log.Printf("Serving at %s...", bindAddr)
|
|
log.Fatal(http.ListenAndServe(bindAddr, outerMux))
|
|
}
|
|
|
|
func main() {
|
|
|
|
log.Println(VERSION)
|
|
|
|
a, err := NewApp("nmdc-webfrontend.conf")
|
|
if err != nil {
|
|
log.Fatal(err.Error())
|
|
return
|
|
}
|
|
|
|
a.RunServer()
|
|
}
|